DDIA·مطالعهSTUDY

نقشه راهRoadmap/ فصل ۱۰Chapter 10

سازگاری و اجماعConsistency and Consensus

این فصل سه نیاز را جدا می‌کند: تازه بودنِ خواندن، ترتیبِ رویدادها و توافقِ گره‌ها بر تصمیم. خطی‌پذیری، ساعتِ منطقی و اجماع ابزارهای مرتبطی هستند، اما جای یکدیگر را نمی‌گیرند. From the precise “behave like one copy” definition (linearizability) through IDs and logical clocks to consensus — nodes agreeing on one order despite failures — plus each guarantee's price: CAP, FLP and the latency floor.

۱۰10 ۱۰ نمودار + روایت گام‌به‌گامdiagrams with step-by-step narration· محور: linearizability · ساعت منطقی · consensus · Raft/Paxos · ZooKeeperFocus: linearizability · logical clocks · consensus · Raft/Paxos · ZooKeeper· منبع: فصل ۱۰ کتاب DDIA ویرایش ۲Source: DDIA 2nd Edition, Chapter 10

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

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

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

مثال فنی: کدام خواندن باید تازه باشد؟

وضعیت اولیه: کاربر نام کاربری sara را ثبت کرده و پاسخ موفق گرفته است.

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

نتیجه و دلیل: این مسیر به تصمیم هماهنگ دربارهٔ یکتایی نیاز دارد؛ نمایش تعداد بازدید ممکن است تأخیر را تحمل کند و تضمین دیگری بخواهد.

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

linearizability means a multi-node system behaves as if there were only one copy of the data with atomic operations on it: once a write succeeds, every reader must see the new value. This guarantee simplifies the programmer's job but has a price (latency, quorum dependence). If you want the same guarantee with fault tolerance, you arrive at consensus: all nodes agreeing on one single order of operations, even when messages arrive late or a node dies.
مسئله‌ای که داریتضمینِ لازممکانیزمِ پاسخ
همیشه جدیدترین مقدار را بخوانم linearizability (تضمینِ تازگی) خواندن/نوشتن از رهبرِ مطمئن، یا رأیِ quorum برای هر عملیات
برای قفل، lease یا نامِ کاربری فقط یک برنده CAS خطی‌پذیر consensus (یا سرویسِ هماهنگی مثل ZooKeeper)
همه‌ی نودها عملیات‌ها را به یک ترتیب ببینند total order broadcast لاگِ مشترک (shared log) — پایهٔ replication ماشینِ وضعیت
شناسه‌ی یکتا و مرتب برای رکوردها ترتیبِ سازگار با علّیت، یا ترتیبِ خطی‌پذیر timestamp oracle یا ساعتِ منطقی (Lamport / HLC)
همه‌ی شرکت‌کنندگانِ یک تراکنش، یا همه commit یا همه abort atomic commitment 2PC که هسته‌اش با consensus مقاوم شده
در دسترس ماندن هنگام قطعی شبکه، حتی با داده‌ی کهنه سازگاریِ ضعیف‌تر (eventual) چندرهبری / leaderless + حلِ تعارض (فصل ۶)
Problem you haveGuarantee neededAnswering mechanism
Always read the newest value linearizability (recency guarantee) Read/write from a trusted leader, or a quorum vote per operation
Exactly one winner for a lock, lease or username Linearizable CAS consensus (or a coordination service like ZooKeeper)
All nodes see operations in one order total order broadcast A shared log — the basis of state machine replication
Unique, ordered IDs for records Causally consistent order, or linearizable order A timestamp oracle or a logical clock (Lamport / HLC)
All participants of a transaction commit or all abort atomic commitment 2PC with its core hardened by consensus
Stay available during a network cut, even with stale data Weaker consistency (eventual) Multi-leader / leaderless + conflict resolution (Chapter 6)
چیدمان فصلChapter layout
بخش ۱ «linearizability» را دقیق تعریف می‌کند، جایگاهش را نشان می‌دهد و هزینه‌هایش را درمی‌آورد. بخش ۲ به مسئله‌ی «شناسه و ترتیب» می‌پردازد — جایی که ساعت‌های منطقی وارد بازی می‌شوند و معلوم می‌شود برای تصمیمِ قطعی به چیزی قوی‌تر نیاز است. بخش ۳ همان «چیزِ قوی‌تر» یعنی consensus است و بخش ۴ ابزارِ آماده‌اش یعنی سرویس‌های هماهنگی را معرفی می‌کند. بخش ۵ درختِ تصمیمِ نهایی است.
Section 1 defines linearizability precisely, shows where it stands and works out its costs. Section 2 turns to “IDs and ordering” — where logical clocks enter and it becomes clear something stronger is needed for a final decision. Section 3 is that “stronger thing”, consensus, and Section 4 introduces its ready-made product, coordination services. Section 5 is the final decision tree.

۱1 خطی‌پذیری (Linearizability)Linearizability

۱.۱. یک نسخه از داده، یک تضمین تازگی1.1. One copy of the data, one freshness guarantee

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

مثال فنی: پس از پاسخ موفق

وضعیت اولیه: SET x=1 کامل شده؛ سپس کلاینت دیگری GET x را آغاز می‌کند و نوشتن تازه‌ای وجود ندارد.

اجرای مرحله‌به‌مرحله: خواندنِ خطی‌پذیر باید ۱ ببیند؛ replicaِ عقب‌مانده که ۰ می‌دهد این قرارداد را نقض می‌کند.

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

When data spreads across replicas (Chapter 6), inconsistency looms: a read may hit a lagging replica and return stale results. Two philosophies exist: eventual consistency (the application copes with conflicts itself) and strong consistency (the system behaves as if it were one node). Linearizability — also called atomic consistency or external consistency — is the precise definition of that second philosophy.

تعریف در یک جمله: به محض آنکه یک نوشته با موفقیت تمام شد، همه‌ی خواندن‌های بعدی (از هر کلاینتی) باید مقدارِ جدید یا جدیدتر را ببینند. به این می‌گویند تضمینِ تازگی (recency guarantee). مثالِ کتاب: دو نفر که نتیجه‌ی یک بازی را نگاه می‌کنند؛ نفرِ اول صفحه را رفرش می‌کند و نتیجه را می‌بیند و به نفرِ دوم می‌گوید؛ نفرِ دوم رفرش می‌کند ولی به replica عقب‌افتاده می‌رسد و هنوز «بازی ادامه دارد» می‌بیند. اگر هر دو هم‌زمان رفرش کرده بودند، جوابِ متفاوت عجیب نبود؛ اما نفرِ دوم بعد از شنیدنِ خبر رفرش کرده — و این دقیقاً نقضِ خطی‌پذیری است.

The one-sentence definition: as soon as a write completes successfully, every later read (from any client) must see the new value or a newer one. This is the recency guarantee. The book's example: two people watching a match score; the first refreshes, sees the result and tells the second; the second refreshes but lands on a lagging replica and still sees “match in progress”. Had both refreshed simultaneously, different answers would be unsurprising; but the second refreshed after hearing the news — and that is exactly a linearizability violation.

۱.۲. چه چیزی یک سیستم را خطی‌پذیر می‌کند؟1.2. What makes a system linearizable?

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

مثال فنی: عملیات هم‌پوشان

وضعیت اولیه: خواندن x زمانی شروع می‌شود که نوشتن x=1 هنوز پاسخ نداده است؛ مقدار قبلی ۰ است.

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

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

To be precise, we draw operations as timeline bars: bar start = request sent, bar end = response received. The client never knows exactly when processing happened; only that it was somewhere in between. If two bars overlap, those operations are concurrent from the system's view and any order is allowed.

اما «خواندنِ هم‌زمان هر جوابی بدهد» کافی نیست: وگرنه مقدار می‌توانست چند بار بینِ ۰ و ۱ بپرَد و برگردد! قانونِ دوم این است: تصور کنید نقطه‌ای بینِ شروع و پایانِ نوشته هست که مقدار در آن لحظه atomically از ۰ به ۱ می‌پرَد (به آن می‌گویند linearization point). خطی که این نقطه‌ها را به هم وصل می‌کند باید همیشه به جلو برود، هرگز به عقب. یعنی به محض آنکه خواندنی مقدارِ جدید را دید، همه‌ی خواندن‌های بعدی هم باید همان یا جدیدتر را ببینند.

But “concurrent reads may return anything” is not enough: otherwise the value could bounce between 0 and 1 forever! The second rule: imagine a point between the write's start and end where the value flips atomically from 0 to 1 (called the linearization point). The line connecting these points must always move forward, never back. Once a read sees the new value, every later read must see it or something newer.

نمودار ۱ — تایم‌لاینِ خطی‌پذیریDiagram 1 — the linearizability timeline باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۱ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: خواندن x زمانی شروع می‌شود که نوشتن x=1 هنوز پاسخ نداده است؛ مقدار قبلی ۰ است.

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

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

نمودار دو بند دارد. بندِ بالا با عنوانِ «یک اجرای خطی‌پذیر» شروع می‌شود: سه ردیفِ رنگی داریم به نام‌های client A و client B و client C. روی ردیفِ C یک نوارِ بنفش است به نام write(x, 1) ⇒ OK — یعنی C دارد مقدارِ x را از ۰ به ۱ تغییر می‌دهد. روی ردیفِ A دو نوارِ خواندن می‌بینی: اولی read(x) ⇒ 0 که تمام و کمال قبل از شروعِ نوشته قرار گرفته، پس حتماً باید مقدارِ کهنه یعنی ۰ را برگرداند. دومی read(x) ⇒ 1 که با نوشته هم‌پوشانی دارد.

حالا خطِ عمودیِ خط‌چینِ زرد را دنبال کن: از بالای بند تا پایینِ نوارِ C کشیده شده و زیرش نوشته «نقطه‌ی خطی‌سازی — x در همین‌جا به‌طور اتمی ۱ می‌شود». این نقطه داخلِ نوارِ نوشته است — جایی بینِ شروع و پایانش. نوارِ اولِ A سمتِ چپِ این خط است (پس ۰)، نوارِ دومِ A از روی آن می‌گذرد (و ۱ برمی‌گرداند)، و نوارِ سبزِ read(x) ⇒ 1 روی ردیفِ B کاملاً سمتِ راستِ آن است.

پیوندِ مهم همان فلشِ سبزِ کوچک بینِ انتهای نوارِ دومِ A و ابتدای نوارِ B است که کنارش نوشته «ترتیبِ زمانیِ واقعی»: خواندنِ B بعد از تمام‌شدنِ خواندنِ A شروع شده، پس حتی اگر نوشته هنوز تمام نشده باشد، B هم باید ۱ ببیند.

بندِ پایین همان قانون را به‌شکلِ یک نقض نشان می‌دهد: خواننده‌ی ۱ نوارِ read(x) ⇒ 1 دارد؛ بعد فلشِ صورتی به سمتِ نوارِ قرمزِ خواننده‌ی ۲ می‌رود که رویش نوشته read(x) ⇒ 0 — STALE و کنارِ فلش جمله‌ی «۲ بعد از شنیدنِ نتیجه توسط ۱ دوباره خواند». این همان اشتباهِ معروف است: خواندن به عقب برگشته. جعبه‌ی سمتِ راست قاعده را جمع می‌کند: «تا وقتی مقدارِ جدیدی بازنویسی نشده، هیچ خواندنِ بعدی نباید از آنچه قبلاً دیده‌ایم کهنه‌تر باشد.»

Read the top half as one linearizable execution across three rows, client A, client B and client C. C's purple bar write(x, 1) ⇒ OK flips x from 0 to 1. A's first read read(x) ⇒ 0 sits entirely before the write, so it must return the old value 0; the second read read(x) ⇒ 1 overlaps the write.

The dashed yellow vertical line is the linearization point — the instant x atomically becomes 1, somewhere inside the write bar. A's first bar is left of it (hence 0), the second crosses it (hence 1), and B's green read(x) ⇒ 1 is fully right of it. The small green arrow between A's second bar and B's bar marks real-time order: B started after A's read finished, so B must see 1 too, even if the write hasn't finished.

The bottom half shows the violation: reader 1 returns read(x) ⇒ 1, then a pink arrow leads to reader 2's red read(x) ⇒ 0 — STALE bar — reader 2 re-read after hearing reader 1's result. Time moved backward. The rule box on the right sums it up: once a new value is visible, no later read may return anything older.

۱.۳. خطی‌پذیری در برابر سریال‌پذیری1.3. Linearizability vs serializability

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

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

وضعیت اولیه: دو تراکنش انتقال را می‌توان به یک ترتیب serial توضیح داد.

اجرای مرحله‌به‌مرحله: اما خواندن جداگانه‌ای که بعد از پاسخ commit آغاز شده، ممکن است از replica قدیمی بیاید، اگر سیستم فقط serializability وعده داده باشد.

نتیجه و دلیل: serializability دربارهٔ تداخل تراکنش‌هاست؛ linearizability ترتیب زمان واقعیِ عملیات را می‌خواهد. ترکیب مناسب به strict serializability می‌رسد.

The two words sound alike but belong to different axes, and confusing them is a classic interview mistake.

LinearizabilitySerializability
درباره‌ی چیست تضمینِ تازگی برای یک شیء (register: یک کلید، یک سطر، یک سند) سطحِ ایزوله‌سازیِ تراکنش‌ها؛ هر تراکنش می‌تواند چند شیء را بخواند و بنویسد
چه چیزی را تضمین می‌کند اگر عملیاتِ A قبل از شروعِ B تمام شود، B باید وضعیتِ هم‌تازه‌ی A یا جدیدتر را ببیند نتیجه‌ی تراکنش‌ها انگار به‌صورتِ سریال اجرا شده‌اند؛ ترتیبِ سریال می‌تواند با ترتیبِ واقعی فرق کند
چه چیزی را حل نمی‌کند مشکلاتِ چندشیء‌ای مثل write skew را حل نمی‌کند خواندنِ کهنه را ممنوع نمی‌کند؛ یک اجرای serializable می‌تواند داده‌ی قدیمی برگرداند
LinearizabilitySerializability
About A recency guarantee for one object (register: one key, one row, one document) Transaction isolation level; each transaction may read and write many objects
Guarantees If operation A finishes before B starts, B must see A's state or newer Transactions look as if executed serially; the serial order may differ from real time
Does not fix Multi-object problems like write skew Does not forbid stale reads; a serializable execution may return old data

ترکیبِ هر دو یعنی strict serializability (یا strong-1SR). پایگاه‌داده‌های single-node معمولاً خطی‌پذیرند؛ اما در دنیای توزیع‌شده قضیه جالب می‌شود: CockroachDB هم سریال‌پذیری می‌دهد و هم مقداری تضمینِ تازگی، اما نه strict serializability — چون هماهنگیِ بینِ تراکنشی گران تمام می‌شود. در مقابل، Spanner و FoundationDB strict serializability می‌دهند. و جالب اینکه می‌توانی سطحِ ایزوله‌سازی و مدلِ سازگاری را تقریباً مستقل از هم انتخاب کنی.

Combining both gives strict serializability (or strong-1SR). Single-node databases are usually linearizable; in the distributed world it gets interesting: CockroachDB offers serializability plus some freshness, but not strict serializability — cross-transaction coordination is too costly. Spanner and FoundationDB do offer strict serializability. And you can pick isolation level and consistency model almost independently.

۱.۴. کجا واقعاً لازمش داریم؟1.4. Where do we really need it?

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

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

وضعیت اولیه: A و B هر دو می‌خواهند sara را ثبت کنند.

اجرای مرحله‌به‌مرحله: هر دو روی یک مرجع هماهنگ compare-and-set(empty, owner) یا درج با قید مناسب انجام می‌دهند؛ فقط یکی موفق می‌شود.

نتیجه و دلیل: خواندن «خالی است» و سپس نوشتن مستقل روی دو replica، یکتایی را تضمین نمی‌کند.

Watching a match score is not serious! But there are places where the system genuinely breaks without linearizability:

  • قفل و انتخابِ رهبر: سیستمِ تک‌رهبری باید مطمئن شود فقط یک رهبر دارد (نه split brain). مکانیزمِ lease — هر نودی که lease بگیرد رهبر است — باید خطی‌پذیر باشد: نباید ممکن باشد دو نود هم‌زمان lease بگیرند. سرویس‌هایی مثل ZooKeeper و etcd دقیقاً برای همین ساخته شده‌اند. Oracle RAC حتی برای هر صفحه‌ی دیسک قفلِ خطی‌پذیر دارد و برای همین شبکه‌ی اختصاصیِ بینِ نودها می‌سازد.
  • قیدهای uniqueness: نامِ کاربری یکتا، مسیرِ فایل یکتا، موجودیِ منفی‌نشدنیِ حساب، فروشِ بیش از موجودیِ انبار، رزروِ هم‌زمانِ یک صندلی. اگر بخواهی این قید هنگامِ نوشته‌شدن اعمال شود (یکی از دو نفرِ هم‌زمان خطا بگیرد)، به خطی‌پذیری نیاز داری — درست مثلِ قفل: «ثبتِ نامِ کاربری» یعنی گرفتنِ قفلِ آن نام، یعنی یک CAS. البته قیدِ نرم (مثلاً overbooking پرواز و جبران با غرامت) می‌تواند خطی‌پذیر نباشد؛ ولی قیدِ سختِ کلاسیکِ پایگاه‌داده‌های رابطه‌ای حتماً خطی‌پذیری می‌خواهد.
  • وابستگیِ زمانیِ کانال‌های موازی: وقتی دو مسیرِ ارتباطیِ متفاوت بینِ دو سیستم باشد، سریع‌تر بودنِ یکی از دیگری یعنی مسابقه. این همان چیزی است که در نمودار ۲ می‌بینیم.
  • Locks and leader election: a single-leader system must have exactly one leader (no split brain). The lease mechanism — whoever holds the lease is leader — must be linearizable: two nodes must never hold the lease at once. Services like ZooKeeper and etcd exist precisely for this. Oracle RAC even holds a linearizable lock per disk page, which is why it builds a dedicated interconnect between nodes.
  • Uniqueness constraints: unique usernames, unique file paths, never-negative balances, no overselling inventory, no double-booked seat. To enforce the constraint at write time (one of two concurrent writers errors out), you need linearizability — just like a lock: “registering a username” means taking the lock on that name, i.e. one CAS. A soft constraint (e.g. flight overbooking compensated later) may skip linearizability; but the classic hard relational constraint always needs it.
  • Timing across parallel channels: when two different communication paths link the same two systems, one being faster than the other means a race. That is exactly what Diagram 2 shows.
نمودار ۲ — مسابقه‌ی دو کانالِ ارتباطیDiagram 2 — the two-channel race باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۲ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: A و B هر دو می‌خواهند sara را ثبت کنند.

اجرای مرحله‌به‌مرحله: هر دو روی یک مرجع هماهنگ compare-and-set(empty, owner) یا درج با قید مناسب انجام می‌دهند؛ فقط یکی موفق می‌شود.

نتیجه و دلیل: خواندن «خالی است» و سپس نوشتن مستقل روی دو replica، یکتایی را تضمین نمی‌کند.

این یک sequence است: پنج شرکت‌کننده از چپ به راست ردیف شده‌اند — Web App (مرورگرِ کاربر)، Web Server (هندلرِ آپلود)، File Storage که زیرش نوشته «replicated, lagging»، Message Queue با برچسبِ «async channel»، و Video Transcoder کارگرِ پس‌زمینه. بالای نمودار سه فاز می‌بینی: Store original، Queue job و Race outcome.

فازِ اول: فلشِ upload video از اپ به سرور می‌رود، بعد write video file از سرور به storage، و پاسخِ write ack (replica lagging) برمی‌گردد — یعنی ذخیره تأیید شده ولی replication داخلی هنوز در راه است. فازِ دوم: چون ویدیو برای صف بزرگ است، فقط «دستور» از صف می‌گذرد: enqueue transcode job (file ref) و بعد deliver job (هر دو خطِ خط‌چینِ async‌اند).

فازِ سوم مسابقه را لو می‌دهد: transcoder با fetch original video می‌رود سراغِ storage و جوابِ صورتیِ stale version or missing می‌گیرد — چون مراحلِ ۳ و ۴ از replicationِ کُندِ storage جلو زدند. نتیجه هم پایین نوشته شده: write transcoded copy of OLD video و کارتِ هشدار: نسخه‌ی اصلی و نسخه‌ی ترنسکدشده برای همیشه ناسازگار می‌مانند.

درسِ نمودار در کارتِ آخر است: دو کانالِ ارتباطی بینِ یک جفت سیستم = مسابقه؛ مگر آنکه storage خطی‌پذیر باشد یا کانالِ اضافی «read-your-owns-writes» را تضمین کند. این دقیقاً همان وضعیتِ دو نفرِ بندِ قبل است: آنجا هم کانالِ دوم، صدایِ نفرِ اول بود.

This is a sequence diagram with five participants left to right — Web App (the user's browser), Web Server (upload handler), File Storage tagged “replicated, lagging”, the Message Queue tagged “async channel”, and the Video Transcoder background worker — in three phases: Store original, Queue job and Race outcome.

Phase one: upload video goes from app to server, then write video file from server to storage, and write ack (replica lagging) comes back — the write is acknowledged while internal replication is still in flight. Phase two: the video is too big for the queue, so only the “instruction” travels it — enqueue transcode job (file ref), then deliver job (both dashed async lines).

Phase three exposes the race: the transcoder's fetch original video hits storage and gets the pink stale version or missing — steps 3–4 overtook storage's slow replication. The result at the bottom: write transcoded copy of OLD video, and the warning card: original and transcoded copy stay inconsistent forever. The lesson in the last card: two channels between one pair of systems = a race, unless storage is linearizable or the extra channel guarantees read-your-own-writes — the same situation as the two match-watchers above, where the second channel was the first person's voice.

۱.۵. کدام روشِ replication خطی‌پذیر است؟1.5. Which replication method is linearizable?

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

مثال فنی: رهبرِ هنوز معتبر؟

وضعیت اولیه: A قبلاً رهبر بوده، اما از اکثریت جدا شده و B رهبر تازه شده است.

اجرای مرحله‌به‌مرحله: خواندن محلیِ A می‌تواند قدیمی باشد؛ پروتکل باید اعتبار رهبری و رسیدن state به نقطهٔ لازم را پیش از پاسخ خطی‌پذیر بررسی کند.

نتیجه و دلیل: single-leader بودن اسمِ معماری است؛ خواندن از هر رهبرِ سابق بدون کنترل اعتبار خطی‌پذیر نمی‌شود.

The simplest route to linearizability is having literally one copy of the data — but then you have no fault tolerance. So revisit the Chapter 6 methods and ask which can be made linearizable:

روشخطی‌پذیر؟چرا
تک‌رهبری بالقوه بله تا وقتی همه‌ی خواندن‌ها و نوشتن‌ها از رهبر باشد و مطمئن باشی رهبر کیست. رهبرِ توهمی = نقضِ خطی‌پذیری؛ و با replication غیرهمگام، failover می‌تواند نوشته‌ی commitشده را از بین ببرد
الگوریتم‌های consensus بله (با دقت) تک‌رهبری با انتخابِ رهبرِ خودکار و جلوگیری از split brain — مثل Raft در etcd و Zab در ZooKeeper. ولی خواندنِ بدونِ بررسیِ «هنوز رهبرم؟» می‌تواند کهنه باشد
چندرهبری خیر نوشتنِ هم‌زمان در چند نود + replication غیرهمگام = تعارض‌هایی که باید حل شوند
بدون‌رهبر (Dynamo-style) احتمالاً خیر ادعا می‌شود quorum (w + r > n) کافی است؛ نیست. LWW با ساعتِ دیواری تقریباً قطعاً غیرخطی‌پذیر است
MethodLinearizable?Why
Single-leader Potentially yes As long as all reads and writes go to the leader and you are sure who the leader is. A delusional leader violates linearizability; and with async replication, failover can lose a committed write
Consensus algorithms Yes (carefully) Single-leader with automatic election and no split brain — Raft in etcd, Zab in ZooKeeper. But a read that never checks “am I still leader?” can be stale
Multi-leader No Concurrent writes on several nodes + async replication = conflicts to resolve
Leaderless (Dynamo-style) Probably not Quorum (w + r > n) is claimed to suffice; it doesn't. LWW on wall-clock time is almost certainly not linearizable

برای ساختنِ خطی‌پذیریِ واقعی در Dynamo-style باید read repair را هم‌زمان (قبل از پاسخ) انجام دهی و نویسنده قبل از نوشتن، بیشترین timestamp را از یک quorum بخواند. Riak به‌خاطرِ هزینه این کار را نمی‌کند؛ Cassandra read repair را انجام می‌دهد ولی با ساعتِ دیواری، خطی‌پذیری را از دست می‌دهد. و نکته‌ی مهم: فقط خواندن/نوشتنِ خطی‌پذیر را می‌توان این‌طور ساخت؛ CAS خطی‌پذیر به consensus نیاز دارد.

For true linearizability in Dynamo-style stores you must do read repair synchronously (before responding), and writers must first read the highest timestamp from a quorum. Riak skips this for cost reasons; Cassandra does read repair but with wall-clock time, losing linearizability. And the key point: only linearizable reads/writes can be built this way; linearizable CAS needs consensus.

نمودار ۳ — quorum هم کافی نیستDiagram 3 — quorum is not enough either باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۳ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: A قبلاً رهبر بوده، اما از اکثریت جدا شده و B رهبر تازه شده است.

اجرای مرحله‌به‌مرحله: خواندن محلیِ A می‌تواند قدیمی باشد؛ پروتکل باید اعتبار رهبری و رسیدن state به نقطهٔ لازم را پیش از پاسخ خطی‌پذیر بررسی کند.

نتیجه و دلیل: single-leader بودن اسمِ معماری است؛ خواندن از هر رهبرِ سابق بدون کنترل اعتبار خطی‌پذیر نمی‌شود.

بالای نمودار، ردیفِ بنفشِ writer را می‌بینی با نوارِ write(x, 1) → sent to all three replicas (w = 3). زیرش سه خطِ افقیِ نودها هست: replica n1 و n2 و n3. روی هر خط یک لوزیِ بنفش است با برچسبِ x = 1 arrives — و مهم اینجاست که این لوزی‌ها در یک نقطه نیستند: n1 زودتر مقدارِ جدید را می‌گیرد، n2 و n3 خیلی دیرتر. کنارِ خطوط هم نوشته «هر replica در لحظه‌ای متفاوت از ۰ به ۱ می‌پرَد — replication در پرواز است».

حالا دو کلاینت. client A نوارِ read r = 2 ⇒ 1 دارد و زیرش دو چیپ: n1: 1 و n2: 0 — از دو نود خوانده، یکی جدید و یکی کهنه؛ چون خواندنش با نوشته مسابقه داشت، ۱ قبول است. client B اما نوارِ صورتیِ read r = 2 ⇒ 0 — STALE دارد با چیپ‌های n2: 0 و n3: 0: هر دو نود هنوز عقب‌اند.

فلشِ صورتیِ وسطِ صحنه حکم را صادر می‌کند: «B بعد از برگشتنِ A شروع کرد، ولی مقدارِ کهنه‌تر خواند». یعنی با اینکه قاعده‌ی quorum برقرار است (w=3 و r=2، پس w + r > n)، اجرا هنوز خطی‌پذیر نیست. جعبه‌ی پایین راه‌ها را می‌نویسد: read repair هم‌زمان قبل از پاسخ، خواندنِ بیشترین timestamp از quorum قبل از نوشتن، و اینکه LWW با ساعتِ دیواری هرگز خطی‌پذیر نمی‌شود و CAS خطی‌پذیر به consensus نیاز دارد.

At the top, the purple writer row carries write(x, 1) → sent to all three replicas (w = 3). Below are the three node lines replica n1, n2 and n3, each with a purple diamond x = 1 arrives — and crucially the diamonds are not aligned: n1 gets the new value early, n2 and n3 much later. The note says it plainly: each replica flips from 0 to 1 at a different moment — replication is in flight.

Now the two clients. client A's bar read r = 2 ⇒ 1 carries two chips, n1: 1 and n2: 0 — it read one fresh and one stale node; since its read raced the write, 1 is acceptable. client B's pink bar read r = 2 ⇒ 0 — STALE carries n2: 0 and n3: 0: both nodes still behind.

The pink arrow across the scene delivers the verdict: “B started after A returned, yet read something older”. Quorum holds (w=3, r=2, so w + r > n) and the execution is still not linearizable. The bottom box lists the fixes: synchronous read repair before responding, reading the highest timestamp from a quorum before writing — plus that wall-clock LWW is never linearizable and linearizable CAS needs consensus.

۱.۶. هزینه‌ی خطی‌پذیری: CAP، PACELC و سقفِ تأخیر1.6. The cost of linearizability: CAP, PACELC and the latency floor

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

مثال فنی: بهای قطع ارتباط

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

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

نتیجه و دلیل: CAP این انتخاب هنگام partition را توصیف می‌کند؛ در حالت سالم هم هماهنگیِ دوردست هزینهٔ تأخیر دارد.

Imagine two regions with the link between them cut (each region internally healthy). With multi-leader, each region works independently and writes sync up later. With single-leader, the leader sits in one region; the other can neither write nor take a linearizable read — unavailable for those users. This is not multi-region specific: any network fault at any scale forces the same choice.

CAP، درست فهمیده‌شدهCAP, properly understood
«سه‌تایی، دوتایش را بردار» گمراه‌کننده است: partition یک خطا است، نه یک انتخاب؛ نمی‌توانی انتخابش نکنی. تنها راهِ «بدونِ partition» این است که شبکه نداشته باشی (یک نود) — که آن‌وقت دیگر در دسترسیِ بالا هم نداری. فرمولِ بهتر: هنگامِ partition یا سازگار باش یا در دسترس (CP یا AP). وقتی شبکه سالم است، هر دو را می‌توانی داشت. تعریفِ رسمیِ CAP هم خیلی محدود است: فقط linearizability و فقط partition — آن هم کمتر از ۸٪ِ حوادثِ گوگل. تعمیمش یعنی PACELC: هنگامِ partition بینِ A و C، وگرنه (else) بینِ تأخیرِ کم (L) و C انتخاب کن. امروزه CAP بیشتر ارزشِ تاریخی دارد و با نتایجِ دقیق‌تر جایگزین شده است.
“Pick two of three” is misleading: a partition is a fault, not a choice; you cannot opt out of it. The only “partition-free” setup is having no network (one node) — which also means no high availability. The better formula: during a partition, be consistent or available (CP or AP). With a healthy network you can have both. The formal CAP definition is also very narrow: only linearizability and only partitions — under 8% of Google's incidents. Its generalization is PACELC: during a partition choose between A and C, else choose between low latency (L) and C. Today CAP is mostly of historical value, superseded by sharper results.

نکته‌ی غافلگیرکننده‌ی پایانی: خطی‌پذیری حتی در یک کامپیوتر هم کمیاب است! حافظه‌ی RAM روی CPU چندهسته‌ای خطی‌پذیر نیست — به‌خاطرِ cache و store buffer هر هسته — مگر با memory barrier. آنجا دلیل، عملکرد است نه تحملِ خرابی؛ و در پایگاه‌داده‌های توزیع‌شده هم بیشترِ دلایلِ کنارگذاشتنِ خطی‌پذیری، عملکرد است. و سرانجام یک اثباتِ مهم (Attiya & Welch): پاسخ‌زمانیِ خواندن/نوشتنِ خطی‌پذیر دست‌کم متناسب با عدم قطعیتِ تأخیرِ شبکه است. در شبکه‌ای با تأخیرِ متغیر (یعنی همه‌ی شبکه‌ها)، خطی‌پذیری ناگزیر کُند است؛ الگوریتمِ سریع‌ترِ خطی‌پذیر وجود ندارد — فقط مدل‌های ضعیف‌تر می‌توانند سریع باشند.

The surprising footnote: linearizability is rare even inside one computer! RAM on a multi-core CPU is not linearizable — each core's cache and store buffer see to that — except behind a memory barrier. There the motive is performance, not fault tolerance; and in distributed databases most reasons for dropping linearizability are likewise performance. Finally an important proof (Attiya & Welch): linearizable read/write latency is at least proportional to network delay uncertainty. On a variable-latency network (i.e. all networks), linearizability is unavoidably slow; a faster linearizable algorithm does not exist — only weaker models can be fast.

مفهوم مکمل — بهای خطی‌پذیری در یک نگاهExtra concept — the price of linearizability at a glance

نمودار تکمیلی · CAP، PACELC و سقف تأخیرExtra diagram · CAP, PACELC and the latency floor باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس از نمودارTeacher narration

مثال فنی برای خواندنِ نمودار: بهای قطع ارتباط

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

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

نتیجه و دلیل: CAP این انتخاب هنگام partition را توصیف می‌کند؛ در حالت سالم هم هماهنگیِ دوردست هزینهٔ تأخیر دارد.

نمودار سه وضعیت را کنار هم می‌گذارد: شبکه سالم (هم سازگار و هم در دسترس — انتخابی لازم نیست)، پارتیشن (خطا، نه انتخاب: یا CP یا AP)، و حالت سالمِ PACELC (تأخیر کم یا سازگاری — سریع یا تازه، هرگز هر دو). نوار پایین سقف را می‌گذارد: پاسخ‌زمانی خطی‌پذیر دست‌کم به‌اندازه عدم قطعیت تأخیر شبکه است و الگوریتم سریع‌تری وجود ندارد.

The diagram lays three situations side by side: a healthy network (consistent and available — no choice needed), a partition (a fault, not a choice: CP or AP), and the healthy PACELC case (low latency or consistency — fast or fresh, never both). The bottom strip sets the floor: linearizable latency is at least the network's delay uncertainty, and no faster algorithm exists.

۲2 مولد‌های شناسه و ساعت‌های منطقیID generators and logical clocks

۲.۱. شمارنده‌ی single-node و محدودیت‌هایش2.1. The single-node counter and its limits

شمارندهٔ یک گره شناسه‌های افزایشی می‌دهد، اما ظرفیت و دسترس‌پذیری به همان مسیر وابسته است. ترتیبِ گرفتن شناسه نیز الزاماً ترتیبِ تأییدِ تراکنش‌ها نیست.

مثال فنی: شمارندهٔ یکتا روی یک نود

وضعیت اولیه: سرور اعداد 100,101,102 را به‌ترتیب تخصیص می‌دهد.

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

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

In a single-node database, the auto-increment ID is both compact (64 bits) and preserves record-creation order: sort chat messages by ID and the conversation comes out right. The generator is really a linearizable system: each ID request is one atomic fetch-and-add. But it has three problems: it is a single point of failure, fetching an ID from another region means a round trip across the planet, and it bottlenecks under high write throughput. (Plus you must persist the counter so a restart never reissues an ID.)

۲.۲. طرح‌های توزیع‌شده‌ی شناسه2.2. Distributed ID schemes

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

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

وضعیت اولیه: دو نود بازه‌های 1…1000 و 1001…2000 را گرفته‌اند.

اجرای مرحله‌به‌مرحله: نود دوم ممکن است زودتر ۱۰۰۱ بدهد و نود اول بعداً ۱ بدهد؛ شناسه‌ها هنوز یکتایند.

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

Five alternative families exist. All give uniqueness; they differ in ordering and linearizability.

نمودار ۴ — فضای طرح‌های شناسهDiagram 4 — the ID-scheme space باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۴ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: دو نود بازه‌های 1…1000 و 1001…2000 را گرفته‌اند.

اجرای مرحله‌به‌مرحله: نود دوم ممکن است زودتر ۱۰۰۱ بدهد و نود اول بعداً ۱ بدهد؛ شناسه‌ها هنوز یکتایند.

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

نمودار سه ستون دارد. ستونِ اول با عنوانِ «① Single node» دو کارت دارد: Autoincrement counter (یکتا + ترتیبِ کامل + خطی‌پذیر، ولی SPOF و گلوگاه) و Timestamp oracle که زیرش چند خط توضیح است: یک شمارنده با replication، پایدارکردنِ دسته‌ای از شناسه‌ها و تحویلِ ترتیبی‌شان — اگر نود بمیرد شناسه‌ها جا می‌افتند ولی هرگز تکرار نمی‌شوند؛ و قابلِ shard کردن نیست و همه‌ی مناطق باید از یک نود بپرسند. چیپِ زردِ پایینِ ستون جمع می‌کند: «خطی‌پذیر ✓».

ستونِ دوم «② Distributed generators» است: Sharded counters (یکتا، ولی ترتیب بینِ شاردها گم می‌شود؛ «شناسه‌ی ۱۶ و ۱۷، کدام زودتر؟»)، Preallocated blocks (نود A بازه‌ی ۱ تا ۱۰۰۰ و نود B بازه‌ی ۱۰۰۱ تا ۲۰۰۰؛ یک نوشته‌ی دیرتر می‌تواند شناسه‌ی کوچک‌تر بگیرد)، Random UUID v4 (۱۲۸ بیت، محلی، بدونِ تماس؛ ولی ترتیب تصادفی است)، و Wall-clock + sequence که مثال‌هایش UUIDv7 / ULID / Snowflake است: تقریباً ترتیبِ زمانِ واقعی، ولی skew و جهشِ ساعت می‌تواند ترتیب را برعکس کند — پس «تقریباً قطعاً غیرخطی‌پذیر».

ستونِ سوم «③ Logical clocks» است و سه کارت: Lamport clock که جفتِ (counter, nodeID) می‌دهد، قاعده‌اش counter := max(local, seen) + 1 است و ترتیبِ کاملِ سازگار با علّیت می‌سازد (ولی مفهومِ زمانِ فیزیکی ندارد و خطی‌پذیر نیست)؛ Hybrid logical clock که زمانِ فیزیکی را با همان قاعده ترکیب می‌کند و حتی با جهشِ NTP هم فقط جلو می‌رود؛ و Vector clocks که به‌ازای هر نود یک شمارنده نگه می‌دارد و «هم‌زمان» را از «بعدی» تشخیص می‌دهد، به قیمتِ اندازه‌ی بزرگ. چیپِ زرد: «ترتیبِ علّی ✓ · تازگیِ خطی‌پذیر ✗».

و نوارِ پایینِ نمودار، پلِ بخشِ بعد را می‌زند: «ترتیب‌دادن، تصمیم‌گرفتن نیست» — برای اجرای یک قید، برنده باید بداند کوچک‌ترین timestamp را دارد، که یعنی شنیدن از همه‌ی نودها؛ یک نودِ ناقص کلِ تصمیم را فلج می‌کند. تصمیمِ مقاوم‌دربرابرِ خرابی یعنی consensus.

The diagram has three columns. Column one, “① Single node”, holds two cards: Autoincrement counter (unique + total order + linearizable, but SPOF and bottleneck) and Timestamp oracle — a replicated counter that persists a batch of IDs and hands them out in order: if the node dies, IDs are skipped but never duplicated; it cannot be sharded and every region must ask one node. The yellow chip at the column's foot sums up: “linearizable ✓”.

Column two, “② Distributed generators”: Sharded counters (unique, but cross-shard order is lost — “ID 16 vs 17, which came first?”), Preallocated blocks (node A owns 1–1000, node B 1001–2000; a later write can get a smaller ID), Random UUID v4 (128 bits, local, contact-free, but randomly ordered), and Wall-clock + sequence — UUIDv7 / ULID / Snowflake: roughly real-time ordered, but skew and clock jumps can invert the order — hence “almost certainly not linearizable”.

Column three, “③ Logical clocks”, holds three cards: the Lamport clock emitting (counter, nodeID) pairs under the rule counter := max(local, seen) + 1, building a causal total order (but with no physical-time meaning and no linearizability); the Hybrid logical clock blending physical time with the same rule so it only moves forward even across NTP jumps; and Vector clocks keeping one counter per node to tell “concurrent” apart from “after”, at the price of size. Yellow chip: “causal order ✓ · linearizable freshness ✗”. The bottom strip bridges to the next section: “ordering is not deciding” — to enforce a constraint the winner must know it holds the smallest timestamp, i.e. hear from every node; one broken node paralyzes the whole decision. Fault-tolerant deciding means consensus.

۲.۳. ساعتِ منطقی: Lamport و HLC2.3. Logical clocks: Lamport and HLC

ساعت Lamport وابستگیِ رویدادها را با شمارنده دنبال می‌کند: علت، عدد کوچک‌تری از معلول می‌گیرد. عکسِ این گزاره درست نیست. HLC اطلاعات منطقی را با زمان فیزیکی ترکیب می‌کند.

مثال فنی: Lamport روی ارسال پیام

وضعیت اولیه: شمارندهٔ A برابر ۵ است و پیام با برچسب ۶ می‌فرستد؛ B در عدد ۳ است.

اجرای مرحله‌به‌مرحله: B هنگام دریافت مقدار را به max(3,6)+1=7 می‌رساند. HLC همین ایدهٔ ترتیب را با مؤلفهٔ زمان فیزیکی ترکیب می‌کند.

نتیجه و دلیل: اگر a علت b باشد برچسب a کوچک‌تر است؛ کوچک‌تر بودن برچسب به‌تنهایی اثبات نمی‌کند a علت b بوده است.

Unlike a physical clock, a logical clock is an event-counting algorithm: its number never says “what time is it”, but comparing two numbers says which came first. We want three things from a logical clock: compact unique timestamps; comparability (total order); and an order consistent with causality (if A happened before B, A's timestamp is smaller).

ساعتِ Lamport (۱۹۷۸) ساده است: هر نود یک شمارنده دارد؛ هنگامِ تولیدِ رویداد یکی زیادش می‌کند؛ هر وقت timestamp بزرگ‌تری از نودِ دیگر دید، شمارنده‌اش را به آن می‌رساند. timestamp جفتِ (counter, nodeID) است تا حتی با برابریِ شمارنده هم یکتا بماند. مقایسه: اول شمارنده، بعد شناسه‌ی نود. نتیجه یک ترتیبِ کاملِ سازگار با علّیت است — اما خطی‌پذیر نیست: فقط تضمین می‌کند هر رویدادی از هر چیزی که دیده بزرگ‌تر باشد، نه اینکه «تازه» باشد.

The Lamport clock (1978) is simple: each node keeps a counter; it increments on every local event; whenever it sees a larger timestamp from another node, it catches up to it. The timestamp is the pair (counter, nodeID) so it stays unique even on counter ties. Comparison: counter first, node ID second. The result is a causal total order — but not linearizable: it only guarantees each event exceeds everything it has seen, not that it is “fresh”.

نمودار ۵ — timestampهای Lamport در یک گفتگوDiagram 5 — Lamport timestamps in a chat باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۵ را با صدای بلند بخوانیمTeacher narration

مثال فنی برای خواندنِ نمودار: Lamport روی ارسال پیام

وضعیت اولیه: شمارندهٔ A برابر ۵ است و پیام با برچسب ۶ می‌فرستد؛ B در عدد ۳ است.

اجرای مرحله‌به‌مرحله: B هنگام دریافت مقدار را به max(3,6)+1=7 می‌رساند. HLC همین ایدهٔ ترتیب را با مؤلفهٔ زمان فیزیکی ترکیب می‌کند.

نتیجه و دلیل: اگر a علت b باشد برچسب a کوچک‌تر است؛ کوچک‌تر بودن برچسب به‌تنهایی اثبات نمی‌کند a علت b بوده است.

سه شرکت‌کننده داریم: User A و User B و User C و زیرِ هر سه نوشته «chat node, counter starts at 0» — هر کدام یک نودِ ساعتِ Lamport با شمارنده‌ی صفر. نمودار دو فاز دارد: «پست‌های هم‌زمان (A و C همدیگر را ندیده‌اند)» و «B شمارنده‌ها را ادغام می‌کند و جواب می‌دهد».

در فازِ اول دو فلشِ مستقل می‌بینی: از A به B با برچسبِ msg ts=(1,A) و از C به B با msg ts=(1,C). یادداشتِ کنارِ هر فلش حسابِ شمارنده را نشان می‌دهد: «A: counter 0→1 = ts (1,A)؛ B می‌پذیرد max(0,1)=1» و برای C همین‌طور — هر دو بدونِ اطلاع از هم، شمارنده‌شان را به ۱ رسانده‌اند. پس دو timestamp با شمارنده‌ی برابر داریم که با شناسه‌ی نود از هم جدا می‌شوند.

در فازِ دوم، B که حالا هر دو پیام را دیده شمارنده‌اش ۱ است؛ فلشِ reply ts=(2,B) از B به A می‌رود و یادداشتش می‌گوید «B: counter 1→2 = ts (2,B)؛ A می‌پذیرد max(1,2)=2». حالا ترتیبِ نهایی را ببین: (1,A) < (1,C) < (2,B) — اول شمارنده، بعد شناسه‌ی نود. هر رویدادی که علّیاً قبل از دیگری است، timestamp کوچک‌تری هم دارد. محدودیت هم همان‌جاست: رویدادهای «واقعاً هم‌زمان» با رویدادهای «مرتب ولی بی‌ارتباط» قابل‌تشخیص نیستند؛ برای آن باید سراغِ vector clock بروی.

Three participants — User A, User B and User C — each a Lamport node starting at counter 0 (“chat node, counter starts at 0”), in two phases: concurrent posts (A and C haven't seen each other) and B merging the counters and replying. Phase one shows two independent arrows: A→B labelled msg ts=(1,A) and C→B labelled msg ts=(1,C), each with its counter math (“A: counter 0→1 = ts (1,A); B adopts max(0,1)=1”, same for C) — two equal-counter timestamps disambiguated by node ID.

In phase two B, having seen both messages at counter 1, sends reply ts=(2,B) to A (“B: counter 1→2 = ts (2,B); A adopts max(1,2)=2”). The final order: (1,A) < (1,C) < (2,B) — counter first, node ID second. Every causally-earlier event sorts smaller. The limit sits right there: “truly concurrent” events are indistinguishable from “ordered but unrelated” ones; for that you need a vector clock.

ساعتِ Lamport دو محدودیت دارد: هیچ ربطی به زمانِ فیزیکی ندارد (پس «پیام‌های امروز» را نمی‌توانی با آن فیلتر کنی)، و اگر دو نود هرگز با هم حرف نزنند، شمارنده‌هایشان می‌تواند وحشتناک فاصله بگیرد. ساعتِ منطقیِ هیبریدی (HLC) ترکیبِ هر دو است: مثلِ ساعتِ دیواری ثانیه می‌شمارد، ولی مثلِ Lamport وقتی timestampِ بزرگ‌تری دید جلو می‌رود؛ و در هر تولیدِ timestamp یکی هم اضافه می‌کند تا حتی با جهشِ رو‌به‌عقبِ NTP فقط جلو برود (یعنی ممکن است کمی از ساعتِ فیزیکی جلوتر باشد). نتیجه: timestampی که تقریباً مثلِ ساعتِ دیواری استفاده می‌شود، با ترتیبِ سازگار با علّیت — بدونِ سخت‌افزارِ خاص و فقط با ساعتِ تقریباً همگام. CockroachDB از HLC استفاده می‌کند.

The Lamport clock has two limits: no relation to physical time (so you cannot filter “today's messages” with it), and counters can drift wildly apart if two nodes never talk. The hybrid logical clock (HLC) blends both: it ticks like wall-clock time but jumps forward Lamport-style on seeing a larger timestamp; and it adds one on every timestamp it mints, so even a backward NTP jump only moves it forward (possibly slightly ahead of physical time). The result: a timestamp usable almost like wall-clock time, with causal order — no special hardware, just roughly-synchronized clocks. CockroachDB uses HLC.

Lamport/HLC در برابر vector clockLamport/HLC vs vector clocks
Lamport و HLC برای تولیدِ transaction ID در MVCC عالی‌اند (اسنپ‌شات با علّیت سازگار می‌ماند). اما وقتی دو timestamp متفاوت می‌بینی، نمی‌توانی بفهمی «هم‌زمان بودند» یا «یکی قبل از دیگری». Vector clock همین را می‌گوید: به‌ازای هر نود یک شمارنده؛ اگر A در یک نود بزرگ‌تر و B در نودِ دیگر بزرگ‌تر باشد، یعنی هم‌زمان بوده‌اند. قیمتش اندازه‌ی timestamp است: یک عدد به‌ازای هر نودِ سیستم.
Lamport and HLC are perfect for minting transaction IDs in MVCC (snapshots stay causally consistent). But seeing two different timestamps, you cannot tell “concurrent” from “one before the other”. The vector clock answers exactly that: one counter per node; if A is bigger on one node and B on another, they were concurrent. Its price is timestamp size: one number per node in the system.

۲.۴. مولدِ شناسه‌ی خطی‌پذیر و چراییِ consensus2.4. The linearizable ID oracle and why consensus

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

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

وضعیت اولیه: درخواست اول شناسهٔ ۱۰۷ گرفته و پاسخ آن کامل شده؛ درخواست دوم بعداً آغاز می‌شود.

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

نتیجه و دلیل: برای این تضمین، هماهنگیِ ترتیب تخصیص لازم است؛ UUID صرفاً مسئلهٔ یکتایی را هدف می‌گیرد.

Why isn't Lamport enough? Because linearizability demands a real-time rule: if A's work finished and B's started after, B must get the bigger ID — even if the two nodes never spoke. The book's example: a user first makes their account private, then posts a photo from a laptop; but photo and settings live on different shards and the photo shard's counter lags. Result: the photo gets the smaller timestamp, and in MVCC a viewer whose timestamp falls between the two still sees the account as “public” alongside the private photo! The simple fix: a linearizable ID oracle.

ساده‌ترین پیاده‌سازی همان «تک‌نودِ مطمئن» است: شمارنده‌ی اتمی + پایدارسازی + replication تک‌رهبری. در عمل به آن timestamp oracle می‌گویند (الگوی Percolator در TiKV). بهینه‌سازی: به‌جای هر درخواست، یک دسته از شناسه‌ها را پایدار و replicate کن و یکی‌یکی تحویل بده — بعد از crash شناسه‌ها جا می‌افتند ولی تکراری یا وارونه صادر نمی‌شود. نمی‌توانی این مولد را shard کنی (ترتیبِ خطی‌پذیر از بین می‌رود) و نمی‌توانی بینِ مناطق پخشش کنی — ولی کارش آن‌قدر ساده است که یک نود بارِ زیادی برمی‌دارد. جایگزینِ بدونِ ارتباط: روشِ Spanner — ساعتِ فیزیکی که به‌جای عدد بازه‌ی عدم قطعیت می‌دهد و سیستم به اندازه‌ی عرضِ بازه صبر می‌کند. نیازمندِ سخت‌افزار/نرم‌افزارِ ساعتِ دقیق است.

The simplest implementation is the same “reliable single node”: atomic counter + persistence + single-leader replication. In practice it is called a timestamp oracle (the Percolator pattern in TiKV). The optimization: persist and replicate a batch of IDs per request and hand them out one by one — after a crash IDs are skipped but never duplicated or inverted. You cannot shard this oracle (linearizable order would die) nor spread it across regions — but its job is so simple one node carries heavy load. The contact-free alternative is Spanner's approach — a physical clock emitting an uncertainty interval instead of a number, with the system waiting out the interval's width. It needs precise-clock hardware/software.

آخرین پل: ساعتِ منطقی برای قیدها کافی نیستFinal bridge: logical clocks are not enough for constraints
برای قفل یا نامِ کاربری، با ساعتِ منطقی می‌توانی به درخواست‌ها timestamp بدهی و «کوچک‌ترین» را برنده اعلام کنی. ولی نودِ برنده چطور می‌فهمد کوچک‌ترین را دارد؟ باید از هر نودِ دیگری که ممکن است timestamp صادر کرده باشد بشنود. اگر یکی از آن‌ها ناقص یا غیرقابل‌دسترس باشد، کلِ سیستم می‌ایستد — یعنی تحملِ خرابی نداریم. برای قفل‌ها و leaseهای مقاوم‌دربرابرِ خرابی به چیزی قوی‌تر از ساعت نیاز داریم: consensus.
For a lock or a username, logical clocks can timestamp requests and declare the “smallest” the winner. But how does the winning node know it holds the smallest? It must hear from every other node that might have issued a timestamp. If one of them is broken or unreachable, the whole system halts — no fault tolerance. For crash-resilient locks and leases you need something stronger than clocks: consensus.

۳3 اجماع (Consensus)Consensus

۳.۱. یک مسئله، چند شکل3.1. One problem, many faces

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

مثال فنی: یک تصمیم با چند اسم

وضعیت اولیه: سه نود باید معلوم کنند چه کسی مالک job J7 است.

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

نتیجه و دلیل: پذیرفتن مالک متفاوت در هر نود با نام «اجماع» سازگار نیست؛ پروتکل باید یک تصمیم معتبر مشترک بدهد.

The three examples of this chapter — split-brain-free leader election, a crash-resilient ID oracle, and failure-resilient CAS — are all one problem: consensus, getting several nodes to agree on one value. It is among the most fundamental and hardest problems in distributed computing, and many systems have implemented it wrongly through history. The famous algorithms: Viewstamped Replication, Paxos, Raft and Zab. All work in the non-Byzantine model (messages may arrive late or vanish, nodes may die, but nobody lies). Byzantine algorithms (assuming fewer than a third of nodes hostile) belong to blockchains and multi-party settings.

نتیجه‌ی FLP: پس چطور ممکن است؟The FLP result: so how is it possible?
نتیجه‌ی معروفِ FLP (Fischer–Lynch–Paterson) می‌گوید: اگر خطرِ crash یک نود وجود داشته باشد، هیچ الگوریتمی نمی‌تواند همیشه به consensus برسد. اما دو نکته قضیه را عملی می‌کند: اول اینکه FLP فقط می‌گوید نمی‌توان پایان (termination) را تضمین کرد، نه اینکه consensus ناممکن است؛ دوم اینکه اثبات برای الگوریتمِ قطعی در مدلِ ناهمگام است — یعنی بدونِ هیچ timeout یا شانسی. اگر اجازه بدهی الگوریتم با timeout حدس بزند نودی مرده (حتی گاهی اشتباه)، یا فقط از تصادفی‌بودن استفاده کند، consensus حل می‌شود.
The famous FLP result (Fischer–Lynch–Paterson) says: if any node may crash, no algorithm can always reach consensus. But two caveats make it practical: FLP only says termination cannot be guaranteed, not that consensus is impossible; and the proof covers deterministic algorithms in the asynchronous model — i.e. with no timeouts or luck. Let the algorithm guess node death via timeout (sometimes wrongly), or use randomness, and consensus is solvable.

۳.۲. چهره‌های مختلف consensus و خواص چهارگانه3.2. The many faces of consensus and the four properties

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

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

وضعیت اولیه: سه رأی‌دهنده پیشنهادهای A و B را برای رهبر دارند.

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

نتیجه و دلیل: خواص agreement، validity، integrity و termination با هم قرارداد می‌سازند؛ داشتن پیام رأی به‌تنهایی کافی نیست.

Consensus wears many faces: agreement on one value (like atomic CAS), building an append-only log (called total order broadcast), the fetch-and-add operation, and atomic commitment of a distributed transaction. The deep point: they are all equivalent — a solution to one converts into a solution to the rest.

نمودار ۶ — چهره‌های consensusDiagram 6 — the faces of consensus باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۶ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: سه رأی‌دهنده پیشنهادهای A و B را برای رهبر دارند.

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

نتیجه و دلیل: خواص agreement، validity، integrity و termination با هم قرارداد می‌سازند؛ داشتن پیام رأی به‌تنهایی کافی نیست.

این نمودارِ ساختاری، یک قلب و چند بازو دارد. قلب، جعبه‌ی بزرگِ وسط است با عنوانِ CONSENSUS و دو زیرنویس: «یک مقدارِ توافق‌شده، یک ترتیبِ توافق‌شده» و «safety همیشه، liveness با اکثریت».

ردیفِ بالا سه جعبه دارد که هر کدام با فلشِ سبز و برچسبِ «≡ equivalent» به قلب وصل‌اند: Single-value consensus (نودها مقدار پیشنهاد می‌کنند، یکی انتخاب می‌شود — انتخابِ رهبر، ادعای lease، ثبتِ نامِ کاربری)، Shared log / total order broadcast (افزودنِ entry به یک ترتیبِ واحد، با «consensus number ∞» — شکلی که بیشترِ سیستم‌ها واقعاً عرضه می‌کنند) و Linearizable CAS (مقایسه-و-تنظیم روی یک register، برای قفل و قیدِ uniqueness).

ردیفِ وسط دو جعبه‌ی دیگر را کنارِ قلب می‌گذارد: Atomic commitment (همه commit یا همه abort — consensus با حقِ وتو: یک رأیِ abort یعنی abort؛ پایه‌ی 2PC) و Fetch-and-add که با برچسبِ «consensus number 2» استثنای جمع است: فقط بینِ دو پیشنهاددهنده تصمیم می‌گیرد.

ردیفِ پایین با فلش‌های «yields» از قلب بیرون می‌آید: State machine replication (همان لاگ + اجرای قطعی = replicaهای یکسان)، Fencing tokens (index هر entry یک عددِ افزایشی است — zxid و revision — که نوشته‌های زامبی را می‌کُشد) و Leader election & leases (تصمیمِ یک‌بار برای هر epoch؛ بدونِ split brain).

و دو جعبه‌ی پایینِ صفحه را آخر بخوان: «چه چیزی را باید تضمین کنی» — چهار خاصیتِ Uniform agreement (هیچ دو نودی متفاوت تصمیم نمی‌گیرند)، Integrity (تصمیم تغییر نمی‌کند)، Validity (فقط مقدارِ پیشنهادشده می‌تواند برنده شود) و Termination (هر نودِ زنده‌ای بالاخره تصمیم می‌گیرد — تنها خاصیتِ liveness و نیازمندِ اکثریتِ زنده). سه‌تای اول safety‌اند و حتی با خرابیِ اکثریت هم نباید بشکنند؛ فقط پیشرفت می‌ایستد. جعبه‌ی کناری هم «دامِ FLP» را یادآوری می‌کند و اینکه Raft و Paxos و VR و Zab در عمل، لاگِ مشترکِ با failoverِ خودکارند.

This structural diagram has one heart and several limbs. The heart is the big center box CONSENSUS — “one agreed value, one agreed order”, with “safety always, liveness with a majority”. The top row holds three boxes joined to the heart by green “≡ equivalent” arrows: Single-value consensus (nodes propose, one wins — leader election, lease claims, username registration), Shared log / total order broadcast (appending entries to one order, with “consensus number ∞” — the shape most systems actually ship) and Linearizable CAS (compare-and-set on one register, for locks and uniqueness).

The middle row parks two more boxes beside the heart: Atomic commitment (all commit or all abort — consensus with veto rights: one abort vote means abort; the basis of 2PC) and Fetch-and-add, the odd one out tagged “consensus number 2”: it only decides between two proposers. The bottom row flows out of the heart via “yields” arrows: State machine replication (the log + deterministic execution = identical replicas), Fencing tokens (each entry's index is a rising number — zxid, revision — that kills zombie writes) and Leader election & leases (one decision per epoch; no split brain).

Read the two bottom boxes last: “what you must guarantee” — the four properties Uniform agreement (no two nodes decide differently), Integrity (the decision never changes), Validity (only a proposed value may win) and Termination (every live node eventually decides — the only liveness property, needing a live majority). The first three are safety and must hold even in catastrophe; only progress stalls. The side box recalls the “FLP trap” and notes that Raft, Paxos, VR and Zab are, in practice, shared logs with automatic failover.

۳.۳. از تک‌رهبری به consensus: epoch و دو دور رأی3.3. From single-leader to consensus: epochs and two rounds of voting

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

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

وضعیت اولیه: در گروه پنج‌نودی، A در term ۷ بوده و B با سه رأی در term ۸ انتخاب می‌شود.

اجرای مرحله‌به‌مرحله: رأی‌دهنده در هر term طبق پروتکل رأی می‌دهد و پیام term قدیمی رد می‌شود؛ انتخاب نامزد باید شرط تازگی لاگ را هم رعایت کند.

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

In traditional single-leader setups a human performed failover — meaning downtime and a violated termination property. Consensus wants automatic leader election, but there is a paradox: electing a leader needs consensus, and consensus needs a leader! The way out: algorithms don't guarantee “one leader at every moment”; they define an epoch number (in Paxos a ballot, in VR a view, in Raft a term) and only guarantee one leader per epoch.

وقتی نودی مدتی از رهبر خبری نشنود، انتخاباتِ جدیدی با شماره‌ی epoch بزرگ‌تر شروع می‌کند. اگر دو رهبرِ مربوط به دو epoch با هم تعارض کنند، رهبرِ با epoch بزرگ‌تر برنده است. و قبل از آنکه رهبرِ جدید اجازه‌ی append به لاگ را داشته باشد، باید از یک quorum (معمولاً اکثریت) رأی بگیرد تا مطمئن شود رهبرِ با epoch بالاتری در کار نیست. نتیجه: دو دور رأی‌گیری — یکی برای انتخابِ رهبر، یکی برای تصویبِ هر entry — و quorum این دو باید هم‌پوشانی داشته باشند.

When a node hears nothing from the leader for a while, it starts a new election with a bigger epoch number. If two leaders from two epochs conflict, the higher-epoch leader wins. And before the new leader may append to the log, it must collect votes from a quorum (usually a majority) to be sure no higher-epoch leader exists. Result: two rounds of voting — one to elect the leader, one to approve each entry — and the two quorums must overlap.

این شبیهِ 2PC است؟ نه!Like 2PC? No!
هر دو «دو مرحله رأی» دارند، ولی تفاوت‌ها اساسی است: در consensus هر نودی می‌تواند انتخابات را شروع کند و فقط پاسخِ quorum لازم است؛ در 2PC فقط هماهنگ‌کننده رأی می‌گیرد و به تک‌تک شرکت‌کنندگان نیاز دارد (یکی ندهد، همه می‌ایستند). به همین دلیلِ ساده، کُردیناتورِ 2PC نقطه‌ی تکیِ خرابی می‌ماند مگر آنکه خودش با consensus مقاوم شود.
Both have “two voting rounds”, but the differences are fundamental: in consensus any node may start an election and only a quorum's replies are needed; in 2PC only the coordinator collects votes and it needs every single participant (one silence stalls everything). For exactly this reason the 2PC coordinator stays a single point of failure unless hardened by consensus itself.

۳.۴. لاگِ مشترک و replication ماشینِ وضعیت3.4. The shared log and state machine replication

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

مثال فنی: همان لاگ، همان state

وضعیت اولیه: لاگِ commitشده شامل set(x,1) و سپس increment(x) است.

اجرای مرحله‌به‌مرحله: هر replica سالم هر دو را به همان ترتیب اعمال می‌کند و به x=2 می‌رسد.

نتیجه و دلیل: state machine باید برای همان ورودی‌ها نتیجهٔ یکسان بدهد؛ NOW() یا تصادفِ مستقل داخل apply می‌تواند replicaها را واگرا کند.

Most consensus systems in practice ship a shared log (the total-order-broadcast equivalent): Raft, VR and Zab do out of the box, and Paxos is used in practice as Multi-Paxos. The log is a superb replication tool: if each entry is one database write and all replicas apply the same entries in the same order with deterministic logic, all reach one final state — the idea called state machine replication (also the basis of event sourcing).

و از همین لاگ می‌توان بقیه‌ی اشکالِ consensus را هم ساخت: مقدارِ اولِ لاگ = پاسخِ consensus تک‌ارزشی؛ شماره‌ی صندلی در entryها = consensus جداگانه برای هر صندلی؛ جمعِ entryها = شمارنده‌ی fetch-and-add؛ و index هر entry = fencing token (در ZooKeeper به آن zxid می‌گویند).

And the same log builds the other consensus faces: the log's first value = the single-value consensus answer; seat numbers across entries = a separate consensus per seat; summing entries = a fetch-and-add counter; and each entry's index = a fencing token (in ZooKeeper called zxid).

نمودار ۷ — لاگِ مشترک، replication ماشینِ وضعیتDiagram 7 — the shared log, state machine replication باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۷ را با صدای بلند بخوانیمTeacher narration

مثال فنی برای خواندنِ نمودار: همان لاگ، همان state

وضعیت اولیه: لاگِ commitشده شامل set(x,1) و سپس increment(x) است.

اجرای مرحله‌به‌مرحله: هر replica سالم هر دو را به همان ترتیب اعمال می‌کند و به x=2 می‌رسد.

نتیجه و دلیل: state machine باید برای همان ورودی‌ها نتیجهٔ یکسان بدهد؛ NOW() یا تصادفِ مستقل داخل apply می‌تواند replicaها را واگرا کند.

این یک dataflow است: جریانِ داده از چپ به راست در پنج مرحله می‌رود که بالای نمودار شماره‌گذاری شده‌اند: 01 / Propose، 02 / Append، 03 / Total order، 04 / Replicate و 05 / Apply. در مرحله‌ی اول سه جعبه می‌بینی: Client 1 writes و Client 2 writes و Client 3 writes — هر سه با توضیحِ «concurrent / propose value»، یعنی چند کلاینت هم‌زمان پیشنهاد می‌نویسند. فلشِ «propose write» آن‌ها را به جعبه‌ی Leader می‌رساند با توضیحِ «single writer / append entry».

فلشِ «append / log entry» از رهبر به Shared log می‌رود (توضیحِ «entries #1 #2 #3 / total order»): یک ترتیبِ واحد از entryها — همان total order broadcast. بعد فلشِ «replicate / synchronous» به مرحله‌ی Quorum copy می‌رسد («replica copies / majority ack»): هر entry قبل از تأیید به کلاینت، به‌صورتِ هم‌زمان روی یک اکثریت کپی می‌شود و «majority ack» همان رأیِ quorum برای هر entry است. سپس «deliver in order» به Apply in order («in log order / deterministic») و فلشِ «apply» به State machine با توضیحِ «deterministic logic / same final state» می‌رسد: با منطقِ قطعی و ترتیبِ یکسان، همه‌ی replicaها به یک وضعیتِ نهایی می‌رسند.

دو شاخه‌ی فرعی هم از لاگ بیرون می‌روند که قدرتِ لاگ را نشان می‌دهند: Fencing token («monotonic counter / log index = token» — فلشِ «index = token»؛ index هر entry یک توکنِ افزایشی است) و Serial txns («stored procedure / one per entry» — فلشِ «stored procedure»؛ هر entry یک تراکنشِ قطعی باشد → تراکنش‌های سریال‌پذیر). نکته‌ی پایانی: شارد‌کردنِ لاگ (یک لاگ به‌ازای هر shard) مقیاس‌پذیری می‌آورد ولی تضمین‌های بینِشاردی مثل اسنپ‌شاتِ سازگار و foreign key را محدود می‌کند.

This dataflow runs left to right in five numbered stages: 01 / Propose, 02 / Append, 03 / Total order, 04 / Replicate and 05 / Apply. Stage one shows three boxes — Client 1 writes, Client 2 writes, Client 3 writes, all “concurrent / propose value”: several clients proposing at once. The “propose write” arrow delivers them to the Leader (“single writer / append entry”).

The “append / log entry” arrow runs from the leader to the Shared log (“entries #1 #2 #3 / total order”): one single order of entries — total order broadcast. Then “replicate / synchronous” reaches Quorum copy (“replica copies / majority ack”): each entry is synchronously copied to a majority before the client is acknowledged, and “majority ack” is the per-entry quorum vote. “Deliver in order” continues to Apply in order (“in log order / deterministic”), and “apply” to the State machine (“deterministic logic / same final state”): with deterministic logic and identical order, every replica reaches one final state. Two side branches off the log show its power: Fencing token (“monotonic counter / log index = token” — each entry's index is a rising token) and Serial txns (“stored procedure / one per entry” — one deterministic transaction per entry gives serializable transactions). Closing note: sharding the log (one log per shard) buys scalability but limits cross-shard guarantees like consistent snapshots and foreign keys.

۳.۵. ظرایف: سرنوشتِ یک log entry3.5. Subtleties: the fate of one log entry

وجود یک ورودی روی رهبر، به‌معنی تأیید نهایی آن نیست. پروتکل مشخص می‌کند چه زمانی ورودی ماندگار و قابل‌اعمال است و پس از تغییر رهبر چه سرنوشتی دارد.

مثال فنی: وجود روی یک نود، commit نیست

وضعیت اولیه: رهبر entry را روی خودش نوشته، اما پیش از تأیید quorum قطع می‌شود.

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

نتیجه و دلیل: هر entryِ محلی ماندگارِ نهایی نیست؛ شرطِ commit و شرطِ انتخاب رهبر با هم از تاریخچهٔ پذیرفته‌شده محافظت می‌کنند.

Raft, Multi-Paxos, VR and Zab share this skeleton: a quorum vote elects the leader, and each entry needs another vote. The devil is in the details: when the old leader died and a new one arrives, entries the old leader acknowledged must survive. Raft forbids a lagging node from becoming leader (its log must be at least as fresh as a followers' majority); Paxos lets anyone lead but forces the new leader to catch its log up before appending.

نمودار ۸ — چرخه‌ی زندگیِ یک log entryDiagram 8 — the life of a log entry باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۸ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: رهبر entry را روی خودش نوشته، اما پیش از تأیید quorum قطع می‌شود.

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

نتیجه و دلیل: هر entryِ محلی ماندگارِ نهایی نیست؛ شرطِ commit و شرطِ انتخاب رهبر با هم از تاریخچهٔ پذیرفته‌شده محافظت می‌کنند.

این نمودار یک ماشینِ وضعیت است با سه گروه که بالایشان نام‌گذاری شده: Entry lifecycle (مسیرِ اصلی)، Slow quorum and term change (مسیرِ خطاها) و Terminal outcomes (پایان‌ها). مسیرِ اصلی از 01 Proposed شروع می‌شود («client write arrives / next log slot» — درخواستِ نوشته رسید و رهبر مقدار را برای اسلاتِ بعدی پیشنهاد داد)، با فلشِ «append to own log» به 02 Appended می‌رود («leader log, current term»)، بعد با «send to followers» به 03 Replicating («quorum vote on the entry / second vote round» — دورِ دومِ رأی؛ دورِ اول فقط رهبر را انتخاب کرده بود)، بعد با «acked by quorum» به 04 Committed («durable, client gets OK / never lost»)، و با «apply in log order» به 05 Applied («state machine updated»).

حالا مسیرِ خطاها. فلشِ «quorum not reached yet» از Replicating به وضعیتِ Stalled می‌رود («slow or failed nodes / retryable»): نودها کُند یا خراب‌اند و رهبر منتظر می‌ماند — و فلشِ برگشتیِ «retry and resend» از بالای صف‌ها دوباره به Replicating بازمی‌گرداند. مسیرِ جدی‌تر فلشِ «higher term seen» است به Superseded («higher term discovered / leader steps down»): رهبرِ قبلی باید کنار برود.

سرنوشتِ entry از این‌جا قطعی می‌شود: فلشِ «replaced by new leader» به Dropped می‌رود («uncommitted, overwritten» — entryهای تأییدنشده بازنویسی می‌شوند) و فلشِ «client retries» درخت را دوباره به Proposed می‌برد (کلاینت با رهبرِ جدید دوباره تلاش می‌کند). و سمتِ سالمِ ماجرا: فلشِ «committed entry survives» از Applied به Durable می‌رسد («survives every failover»). خطِ بینِ این دو دقیقاً همان «commit» است: قبل از آن هیچ‌چیز قطعی نیست، بعد از آن همه‌چیز ابدی است — و دلیلش قاعده‌ی Raft است: فقط نودی رهبر می‌شود که لاگش دست‌کم به‌روزِ اکثریت باشد.

This state machine has three groups: Entry lifecycle (the main path), Slow quorum and term change (the fault paths) and Terminal outcomes. The main path runs 01 Proposed (“client write arrives / next log slot”) → “append to own log” → 02 Appended (“leader log, current term”) → “send to followers” → 03 Replicating (“quorum vote on the entry / second vote round” — the second round; the first only elected the leader) → “acked by quorum” → 04 Committed (“durable, client gets OK / never lost”) → “apply in log order” → 05 Applied (“state machine updated”).

The fault paths: “quorum not reached yet” leads from Replicating to Stalled (“slow or failed nodes / retryable”) — the leader waits, and “retry and resend” loops back to Replicating. The graver path is “higher term seen” to Superseded (“higher term discovered / leader steps down”): the old leader must step aside. The entry's fate is then sealed: “replaced by new leader” goes to Dropped (“uncommitted, overwritten” — uncommitted entries are rewritten) with “client retries” looping back to Proposed, while “committed entry survives” runs from Applied to Durable (“survives every failover”). The line between the two is exactly “commit”: before it nothing is final, after it everything is eternal — thanks to Raft's rule that only a node at least as fresh as a majority may lead.

سازگاری در برابر دردسترس‌بودنِ انتخابِ رهبرConsistency vs leader-election availability
اگر نودی با داده‌ی کهنه رهبر شود، ممکن است روی entryهایی بنویسد که رهبرِ قبلی نوشته — و خاصیتِ append-only می‌شکند. گاهی آگاهانه این تضمین را ضعیف می‌کنیم تا سریع‌تر برگردیم: مثلاً Kafka گزینه‌ی unclean leader election را دارد که هر replica‌ای می‌تواند رهبر شود، حتی عقب‌افتاده. نتیجه: در دسترسیِ بیشتر، ولی دیگر نظریه‌ی consensus حاکم نیست — تا وقتی خرابی نداشته باشی خوب است؛ با اولین ترکیبِ بدِ خرابی‌ها، داده گم یا خراب می‌شود. نکته‌ی دیگر: برای خواندنِ خطی‌پذیر هم باید رأیِ quorum گرفت (مثلاً در etcd) تا «رهبرِ هنوزمعتبر» تأیید شود؛ و الگوریتم‌ها برای افزودن/حذفِ نود به reconfiguration نیاز دارند.
If a stale node becomes leader, it may overwrite entries the previous leader wrote — breaking append-only. We sometimes knowingly weaken this guarantee to recover faster: Kafka's unclean leader election lets any replica lead, even a lagging one. Result: more availability, but consensus theory no longer rules — fine until the first bad combination of failures loses or corrupts data. Related point: linearizable reads need a quorum vote too (e.g. in etcd) to confirm “still the valid leader”; and algorithms need reconfiguration to add or remove nodes.

۳.۶. مزایا و بهای consensus3.6. The benefits and price of consensus

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

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

وضعیت اولیه: گروه سه‌عضوی برای عملیات نیازمند اکثریت به دو عضو دسترس‌پذیر نیاز دارد.

اجرای مرحله‌به‌مرحله: خرابی یک عضو قابل‌تحمل است؛ با خرابی دو عضو پیشرفت متوقف می‌شود تا اکثریت برگردد.

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

Consensus is really “single-leader done right”: automatic failover, no lost committed data, no split brain — despite all of Chapter 9's troubles. The practical rule: any automatic-failover system not built on a proven consensus algorithm is probably unsafe. A proven algorithm doesn't guarantee the whole system — bugs live elsewhere too — but it is a good start.

  • اکثریتِ سخت‌گیرانه: سه نود برای تحملِ یک خرابی، پنج نود برای دو خرابی. هر عملیات = ارتباط با quorum؛ افزودنِ نود سیستم را کُندتر می‌کند، نه سریع‌تر. در partition فقط بخشِ اکثریت پیش می‌رود.
  • وابستگی به timeout: تشخیصِ خرابی با timeout است. timeout زیاد = بازیابیِ کُند؛ timeout کم = انتخاباتِ مکرر و وقت‌گذرانیِ سیستم برای انتخابِ رهبر به‌جای کارِ مفید. در چندمنطقه‌ای تنظیمش سخت است.
  • حساسیت به شبکه‌ی بد: اگر فقط یک لینک نامطمئن باشد، Raft می‌تواند در حالتی گیر کند که رهبری مدام بینِ دو نود بپرَد یا رهبر مجبور به استعفا شود و سیستم عملاً پیشرفت نکند. راه‌حلِ بعدیِ Raft فازِ pre-vote بود؛ و EPaxos با حذفِ رهبر، در برابرِ نود/لینکِ بد مقاوم‌تر است.
  • Strict majority: three nodes to survive one failure, five for two. Every operation = talking to a quorum; adding nodes makes the system slower, not faster. During a partition only the majority side advances.
  • Timeout dependence: failure detection runs on timeouts. Long timeout = slow recovery; short timeout = constant elections with the system politicking instead of working. Hard to tune across regions.
  • Bad-network sensitivity: a single flaky link can trap Raft in leadership flapping between two nodes, or force the leader to resign so the system makes no progress. Raft's later pre-vote phase answered this; EPaxos drops the leader and resists bad nodes/links better.

مفهوم مکمل — دو دور رأی: consensus در برابر 2PCExtra concept — two voting rounds: consensus vs 2PC

نمودار تکمیلی · consensus در برابر 2PCExtra diagram · consensus vs 2PC باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس از نمودارTeacher narration

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

وضعیت اولیه: در consensus، سه replica از پنج replica ممکن است با خرابی دو عضو پیشرفت کنند.

اجرای مرحله‌به‌مرحله: در 2PC، participantِ prepared که تصمیم coordinator را ندارد ممکن است منتظر بماند؛ رأی yesِ همه برای مسیر commit لازم است.

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

دو ستون را مقایسه کن: در consensus هر دو دور (انتخاب رهبر و تصویب entry) فقط به quorum نیاز دارند و هر نودی می‌تواند شروع کند — یک نود خاموش مشکلی نیست. در 2PC فقط هماهنگ‌کننده رأی می‌گیرد و به تک‌تک شرکت‌کنندگان نیاز دارد — یک سکوت همه‌چیز را متوقف می‌کند. نتیجه در نوار پایین است: هماهنگ‌کننده نقطه تکی خرابی می‌ماند، مگر با consensus مقاوم شود.

Compare the two columns: in consensus both rounds (leader election and entry approval) need only a quorum, and any node may start — one silent node is harmless. In 2PC only the coordinator collects votes and needs every participant — one silence stalls everything. The bottom strip concludes: the coordinator stays a single point of failure unless hardened by consensus.

۴4 سرویس‌های هماهنگی (Coordination Services)Coordination services

۴.۱. ZooKeeper و etcd چه می‌دهند؟4.1. What do ZooKeeper and etcd give you?

سرویس هماهنگی، ابزارهای کوچکی برای نگهداریِ تنظیمات و هماهنگ کردنِ گره‌ها می‌دهد. استفاده از چنین سرویسی، کدِ مصرف‌کننده را از مدیریتِ قطع اتصال و مالکیتِ منقضی بی‌نیاز نمی‌کند.

مثال فنی: قفل و تنظیمات روی etcd

وضعیت اولیه: کلید /jobs/J7/owner و نسخهٔ پیکربندی در سرویس هماهنگی نگهداری می‌شوند.

اجرای مرحله‌به‌مرحله: عملیات شرطی مالک را تعیین می‌کند و watch تغییر را اطلاع می‌دهد؛ مصرف‌کننده بعد از قطع اتصال باید وضعیت را دوباره همگام کند.

نتیجه و دلیل: watch خبر تغییر است و خودش مجوز اثر نیست؛ کارگر همچنان به lease و fencing متناسب با منبع کار نیاز دارد.

ZooKeeper, etcd and Consul look like key-value stores but are not built for high write volume: they exist to coordinate the nodes of some other system. Kubernetes runs on etcd; Spark and Flink in HA mode run on ZooKeeper. Their data is small and fits in memory (though persisted to disk too) and is replicated via consensus over a small fixed cluster (3 or 5 nodes) — Google's Chubby lock-service pattern. Four key capabilities:

  • قفل و lease: با CAS خطی‌پذیرِ consensus؛ اگر چند نود هم‌زمان برای یک lease مسابقه بدهند، فقط یکی برنده می‌شود.
  • پشتیبانی از fencing: هر entry لاگ یک عددِ افزایشی می‌گیرد (zxid در ZooKeeper، revision در etcd) و همین، fencing token رایگان است.
  • تشخیصِ خرابی: کلاینت‌ها sessionِ بلندمدت دارند و heartbeat می‌فرستند؛ تا timeout برقرار باشد lease زنده است، وگرنه سرویس فرض می‌کند کلاینت مرده و lease را آزاد می‌کند (در ZooKeeper: ephemeral node).
  • اعلانِ تغییرات: کلاینت می‌تواند بخواهد به‌محضِ تغییرِ یک کلید خبردار شود (به‌جای polling دائمی) — مثلاً فهمیدنِ پیوستنِ نودِ جدید یا مرگِ نودِ دیگر.
  • Locks and leases: with consensus-backed linearizable CAS; if several nodes race for one lease, exactly one wins.
  • Fencing support: each log entry takes a rising number (zxid in ZooKeeper, revision in etcd) — a free fencing token.
  • Failure detection: clients hold long-lived sessions and send heartbeats; while the timeout holds the lease lives, otherwise the service assumes the client died and frees the lease (in ZooKeeper: an ephemeral node).
  • Change notifications: a client can ask to be told the moment a key changes (instead of polling forever) — e.g. learning a new node joined or another died.
نمودار ۹ — پشته‌ی سرویسِ هماهنگیDiagram 9 — the coordination-service stack باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۹ را با صدای بلند بخوانیمTeacher narration

مثال فنی برای خواندنِ نمودار: قفل و تنظیمات روی etcd

وضعیت اولیه: کلید /jobs/J7/owner و نسخهٔ پیکربندی در سرویس هماهنگی نگهداری می‌شوند.

اجرای مرحله‌به‌مرحله: عملیات شرطی مالک را تعیین می‌کند و watch تغییر را اطلاع می‌دهد؛ مصرف‌کننده بعد از قطع اتصال باید وضعیت را دوباره همگام کند.

نتیجه و دلیل: watch خبر تغییر است و خودش مجوز اثر نیست؛ کارگر همچنان به lease و fencing متناسب با منبع کار نیاز دارد.

نمودارِ معماری از چپ شروع می‌شود: ناحیه‌ای به نامِ App cluster — many nodes با سه نودِ نمونه: «App node A — holds a lease»، «App node B — sends heartbeats»، «App node C — registers endpoint». سه فلش از این ناحیه به مرکز می‌روند: acquire lease، heartbeat و register.

مرکز، جعبه‌ی بزرگِ Coordination service است (ZooKeeper / etcd / Consul، سبکِ Chubby) که داخلِ مرزِ «small fixed cluster: 3 or 5 nodes» قرار گرفته — نکته‌ی مهمِ نمودار: اندازه‌ی این خوشه ثابت است، مستقل از اینکه خوشه‌ی اپ چند هزار نود دارد. زیرش جعبه‌ی Consensus core است (Raft / Zab / Paxos با «in-memory + durable log») و با فلشِ «replicated log» به سرویس وصل است.

از مرکز چهار فلش به ستونِ قابلیت‌ها می‌رود: «atomic ops» به Locks & leases (فقط یک برنده)، «log index» به Fencing tokens (zxid / revision)، «sessions» به Failure detection (session + heartbeat و نودهای موقتی)، و «key changes» به Change notifications (watch به‌جای polling).

و ستونِ راست مصرف‌کننده‌ها هستند: Leader election با فلشِ «elect leader»، Shard assignment با «token» و «assign» (بازتخصیص هنگامِ پیوستن یا خرابی)، Config management با «watch» (پیکربندی را بارگذاری و subscribe کن)، و Service discovery که خطِ خط‌چین و برچسبِ «optional / cache-first» دارد: ثبتِ endpointها راحت است، ولی این کار اغلب به consensus نیاز ندارد و با کشِ DNS هم می‌شود انجامش داد.

Read this architecture diagram from the left: a zone called App cluster — many nodes with three sample nodes — “App node A — holds a lease”, “App node B — sends heartbeats”, “App node C — registers endpoint” — sending three arrows to the center: acquire lease, heartbeat and register. The center is the big Coordination service box (ZooKeeper / etcd / Consul, Chubby-style) inside a “small fixed cluster: 3 or 5 nodes” boundary — the diagram's key point: this cluster's size is fixed, however many thousand nodes the app cluster has — with a Consensus core box below it (Raft / Zab / Paxos, “in-memory + durable log”) joined by a “replicated log” arrow.

Four arrows run from the center to the capability column: “atomic ops” to Locks & leases (exactly one winner), “log index” to Fencing tokens (zxid / revision), “sessions” to Failure detection (sessions + heartbeats and ephemeral nodes), and “key changes” to Change notifications (watches instead of polling). The right column holds the consumers: Leader election via “elect leader”, Shard assignment via “token” and “assign” (reassigned on joins and failures), Config management via “watch” (load config and subscribe), and Service discovery with a dashed “optional / cache-first” tag: registering endpoints is easy, but it rarely needs consensus and DNS caching usually does the job.

۴.۲. کاربردها: پیکربندی، تخصیص کار، service discovery4.2. Uses: config, work assignment, service discovery

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

مثال فنی: اختصاص worker به job

وضعیت اولیه: کنترل‌گر برای job J7 مالک worker-B و نسل 8 را ثبت می‌کند.

اجرای مرحله‌به‌مرحله: workerها تغییر را می‌بینند و مقصد اثر، درخواست نسل ۷ را رد می‌کند.

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

  • مدیریتِ پیکربندی: timeoutها، اندازه‌ی thread pool و امثال آن را به‌صورتِ key-value نگه دار و هنگامِ تغییر خبردار شو. نکته: این کار به consensus نیاز ندارد؛ اگر سرویس را از قبل داری راحت است، وگرنه polling از فایل یا URL هم کار را راه می‌اندازد.
  • تخصیصِ کار به نودها: انتخابِ رهبر برای سرویس‌های stateful و job scheduler؛ و تعیینِ اینکه کدام shard روی کدام نود باشد — با پیوستنِ نودِ جدید جابه‌جاییِ شاردها و با خرابیِ نود، تحویلِ کار به بقیه. ترکیبِ عملیاتِ اتمی + نودِ موقتی + اعلان‌ها این بازیابیِ خودکار را ممکن می‌کند (با کتابخانه‌هایی مثل Apache Curator). و مزیتِ بزرگ: consensus را به ۳–۵ نودِ ثابت «برون‌سپاری» می‌کنی، نه هزاران نودِ سیستمِ اصلی.
  • Service discovery: در ابر، IPها مدام عوض می‌شوند؛ سرویس‌ها endpointشان را ثبت می‌کنند و بقیه پیدایشان می‌کنند. اما اغلب overkill است: این کار به خطی‌پذیری نیاز ندارد و در دسترسی و سرعت مهم‌تر است — پس معمولاً کش کردن بهتر است (مثلِ چندلایه‌ی DNS). ZooKeeper برای همین observer دارد: replicaهایی که لاگ را می‌گیرند ولی رأی نمی‌دهند؛ خواندنشان خطی‌پذیر نیست ولی در دسترس است و توانِ خواندن را بالا می‌برد.
  • Config management: keep timeouts, thread-pool sizes and the like as key-value pairs and get told on change. Note: this needs no consensus; if you already run the service it is convenient, otherwise polling a file or URL does the job.
  • Assigning work to nodes: electing a leader for stateful services and job schedulers; and deciding which shard sits on which node — reshuffling shards when a node joins, handing work over when one fails. Atomic ops + ephemeral nodes + notifications make this automatic recovery possible (with libraries like Apache Curator). And the big win: you “outsource” consensus to a fixed 3–5 nodes, not the main system's thousands.
  • Service discovery: in the cloud IPs churn constantly; services register their endpoints and others find them. But it is usually overkill: this needs no linearizability, and availability and speed matter more — so caching is usually better (like multi-layer DNS). ZooKeeper offers observers for this: replicas that receive the log but don't vote; their reads aren't linearizable but are available and raise read throughput.

۵5 جمع‌بندی: کدام تضمین را انتخاب کنیم؟Wrap-up: which guarantee to choose?

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

مثال فنی: تضمین را برای هر مسیر انتخاب کن

وضعیت اولیه: ثبت نام کاربری نیاز به یکتایی فوری دارد، اما شمارندهٔ بازدید می‌تواند چند ثانیه عقب باشد.

اجرای مرحله‌به‌مرحله: مسیر اول هماهنگی و قید سخت می‌گیرد؛ مسیر دوم جمع‌زدن غیرهم‌زمان با کنترل تکرار دارد.

نتیجه و دلیل: تضمین قوی را بر اساس قانون همان عملیات انتخاب می‌کنیم؛ یک برچسب consistency برای همهٔ بخش‌های محصول کافی نیست.

Now we hold all the pieces: the freshness guarantee (linearizability), the ordering tools (logical clocks), the decision engine (consensus) and its ready-made product (the coordination service). The practical question: how far to go for each need? The tree below takes that decision step by step.

نمودار ۱۰ — درختِ تصمیمِ انتخابِ تضمینDiagram 10 — the guarantee-choice decision tree باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس — نمودار ۱۰ را با صدای بلند بخوانیمTeacher narration

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

وضعیت اولیه: ثبت نام کاربری نیاز به یکتایی فوری دارد، اما شمارندهٔ بازدید می‌تواند چند ثانیه عقب باشد.

اجرای مرحله‌به‌مرحله: مسیر اول هماهنگی و قید سخت می‌گیرد؛ مسیر دوم جمع‌زدن غیرهم‌زمان با کنترل تکرار دارد.

نتیجه و دلیل: تضمین قوی را بر اساس قانون همان عملیات انتخاب می‌کنیم؛ یک برچسب consistency برای همهٔ بخش‌های محصول کافی نیست.

این یک درختِ تصمیم با چهار خط (lane) است که بالایشان شماره خورده: 01 / Decision (سوال‌ها)، 02 / Linearizable، 03 / Weak but sufficient و 04 / Eventual is fine (سه مسیرِ نتیجه). سمتِ راستِ درخت (در RTL یعنی ابتدا)، جعبه‌ی شروع است: Requirement Review با زیرنویسِ «what must readers see? / goal: weakest sufficient» — یعنی هدف این است که ضعیف‌ترین تضمینِ کافی را انتخاب کنی، نه قوی‌ترین را.

اولین سوال این است: Need the latest value? («lock, lease, seat, balance»). اگر جواب «no» باشد، به سوالِ دوم می‌رسی: Causal sanity per user? («read-own-writes, monotonic»). جوابِ «yes» تو را به خطِ سوم می‌اندازد: Weak guarantees («read-your-writes, monotonic / consistent prefix») و Logical clock IDs («Lamport / hybrid clocks / causal order»)، و نتیجه‌ی نهایی همان جعبه‌ی Weak guarantees suffice با توضیحِ «timestamps for ordering / freshness only» است. و اگر به آن هم نیازی نداشتی، مسیرِ «no» مستقیم به خطِ چهارم می‌رود: Eventual consistency («multi-leader / leaderless / offline-friendly»).

اما اگر جوابِ سوالِ اول «yes» بود، واردِ خطِ دوم می‌شوی. سوالِ سوم می‌پرسد Cross-channel timing? («queue + storage share fact» — همان وضعیتِ نمودارِ ۲): جوابِ «yes» یعنی اولین گامِ لازم Linearizable storage است («or read-your-writes channel»، برچسبِ «cross-channel fix»). بعد به سوالِ چهارم می‌رسی: Hard constraint? («uniqueness, balance, seat»). جوابِ «yes» تو را به Consensus-backed CAS می‌رساند («Raft, Paxos, ZooKeeper / etcd / Consul») یا به‌عنوانِ جایگزینِ تک‌نویسی، Timestamp oracle («or single-node CAS / single writer») — و بعد جعبه‌ی هزینه را ببین: Cost: quorum latency با توضیحِ «latency ≥ network delay» و ارجاع به «Attiya-Welch bound». و اگر قیدِ سخت نداشتی، مسیرِ «no» می‌گوید همان تضمین‌های ضعیف‌تر + timestampها کافی‌اند.

درسِ این درخت این نیست که «همیشه consensus بگذار»؛ بلکه این است که قیمتِ هر تضمین را آگاهانه بپردازی: بیشترِ مسیرهای واقعی به ضعیف‌ترین تضمینِ کافی ختم می‌شوند، و فقط قیدهای سخت‌اند که consensus می‌خواهند.

This decision tree has four lanes: 01 / Decision (the questions), 02 / Linearizable, 03 / Weak but sufficient and 04 / Eventual is fine (the three outcome paths). At the tree's start (right side) sits Requirement Review — “what must readers see? / goal: weakest sufficient”: pick the weakest sufficient guarantee, not the strongest.

First question: Need the latest value? (“lock, lease, seat, balance”). A “no” leads to question two: Causal sanity per user? (“read-own-writes, monotonic”). A “yes” drops you into lane three: Weak guarantees (“read-your-writes, monotonic / consistent prefix”) plus Logical clock IDs (“Lamport / hybrid clocks / causal order”), ending at Weak guarantees suffice (“timestamps for ordering / freshness only”). Needing not even that, the “no” path runs straight to lane four: Eventual consistency (“multi-leader / leaderless / offline-friendly”).

A “yes” to question one enters lane two. Question three asks Cross-channel timing? (“queue + storage share fact” — Diagram 2's situation): “yes” means the first required step is Linearizable storage (“or read-your-writes channel”, the “cross-channel fix”). Then question four: Hard constraint? (“uniqueness, balance, seat”). “Yes” leads to Consensus-backed CAS (“Raft, Paxos, ZooKeeper / etcd / Consul”) or, as the single-writer alternative, a Timestamp oracle (“or single-node CAS / single writer”) — then eyeball the cost box: Cost: quorum latency (“latency ≥ network delay”, citing the Attiya-Welch bound). Without a hard constraint, the “no” path says weaker guarantees + timestamps suffice. The tree's lesson is not “always add consensus” but pay each guarantee's price knowingly: most real paths end at the weakest sufficient guarantee; only hard constraints want consensus.

اگر این را می‌خواهیحداقلِ تضمینمکانیزمِ نمونه
خواندنِ همیشه‌تازهlinearizabilityخواندن از رهبرِ تأییدشده یا رأیِ quorum (etcd)
یک برنده برای قفل/نام/صندلیCAS خطی‌پذیرconsensus — مستقیم یا با ZooKeeper/etcd
ترتیبِ یکسانِ عملیات در همه‌جاtotal order broadcastلاگِ مشترک (Raft/Zab/Multi-Paxos)
شناسه‌ی یکتا + ترتیبِ تقریبیترتیبِ زمانِ واقعیUUIDv7 / ULID / Snowflake
شناسه‌ی یکتا + ترتیبِ علّیساعتِ منطقیLamport / HLC (vector برای تشخیصِ هم‌زمانی)
commit قطعی برای همه‌ی شرکت‌کنندگانatomic commit2PC روی consensus
دردسترس‌بودن هنگامِ partitionسازگاریِ ضعیف‌ترچندرهبری/بدون‌رهبری + حلِ تعارض
If you want thisMinimum guaranteeSample mechanism
Always-fresh readslinearizabilityRead from a confirmed leader or a quorum vote (etcd)
One winner for a lock/name/seatLinearizable CASConsensus — direct or via ZooKeeper/etcd
One operation order everywheretotal order broadcastA shared log (Raft/Zab/Multi-Paxos)
Unique ID + rough orderReal-time orderUUIDv7 / ULID / Snowflake
Unique ID + causal orderLogical clockLamport / HLC (vector for concurrency detection)
A firm commit for all participantsatomic commit2PC over consensus
Availability during partitionsWeaker consistencyMulti-leader/leaderless + conflict resolution

نقشه فصل — همه مفاهیم در یک نگاهChapter map — all concepts at a glance

نقشه فصل ۱۰ · consensus و سازگاریChapter 10 map · consensus and consistency باز کردن در صفحه جدا ↗Open standalone ↗
روایت مدرس از نمودارTeacher narration

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

وضعیت اولیه: کاربر نام کاربری sara را ثبت کرده و پاسخ موفق گرفته است.

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

نتیجه و دلیل: این مسیر به تصمیم هماهنگ دربارهٔ یکتایی نیاز دارد؛ نمایش تعداد بازدید ممکن است تأخیر را تحمل کند و تضمین دیگری بخواهد.

گره مرکزی همان پیام فصل است: consensus و سازگاری. سه مفهوم بالا ورودی‌های تصمیم‌اند: خطی‌پذیری (هدف تازگی)، ساعت‌های منطقی (ابزار ترتیب) و CAP/PACELC (بهای تازگی). سه مفهوم پایین خروجی‌های تصمیم‌اند: لاگ مشترک (ترتیب واحد)، replication ماشین وضعیت (وضعیت نهایی یکسان) و سرویس هماهنگی (قفل، fencing، اعلان).

درس یک‌خطی نقشه: اول ضعیف‌ترین تضمین کافی را بخواه — ساعت منطقی و مدل‌های ضعیف‌تر؛ فقط قید سخت (قفل، نام یکتا، موجودی) consensus و quorum می‌خواهد.

The center node is the chapter's message: consensus and consistency. The three top concepts are the decision's inputs: linearizability (the freshness goal), logical clocks (the ordering tool) and CAP/PACELC (freshness's price). The three bottom concepts are its outputs: the shared log (one order), state machine replication (one final state) and the coordination service (locks, fencing, notifications).

The map's one-line lesson: first ask for the weakest sufficient guarantee — logical clocks and weaker models; only hard constraints (locks, unique names, balances) want consensus and quorums.

۶6 بستهٔ مصاحبهInterview pack

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

مفهومتعریفِ یک‌خطینکته‌ی مصاحبه
linearizabilityمثلِ یک نسخه‌ی داده با عملیات‌های اتمیتضمینِ تازگی؛ قوی‌ترین مدلِ سازگاریِ رایج
registerیک شیء/کلید/سطر که خوانده و نوشته می‌شودواحدِ خطی‌پذیری یک شیء است، نه تراکنش
linearization pointلحظه‌ی موهومیِ اتمی‌شدنِ اثرِ عملیاتنقطه‌ها باید همیشه در زمان جلو بروند
recency guaranteeخواندنِ بعدی، دست‌کم به‌تازگیِ خواندنِ قبلیهمان چیزی که در «کهنه خواندن» می‌شکند
strict serializabilityسریال‌پذیری + خطی‌پذیری (strong-1SR)Spanner و FoundationDB بله؛ CockroachDB نه دقیقاً
cross-channel raceدو مسیرِ ارتباطی با سرعتِ متفاوتصف از storage جلو می‌زند → ناسازگاریِ دائمی
quorum (w + r > n)خواندن/نوشتن از مجموعه‌های متقاطعبرای خطی‌پذیری کافی نیست (نمودار ۳)
read repairهمگام‌سازیِ کهنه‌ها قبل از پاسخهم‌زمانش کن تا خطی‌پذیر شود؛ Riak نمی‌کند
CAPهنگامِ partition: سازگاری یا دردسترسی«سه‌تایی دوتا» گمراه‌کننده است؛ partition انتخابی نیست
PACELCpartition: A/C؛ وگرنه: L/Cتأخیرِ عادی را هم واردِ معادله می‌کند
Attiya–Welchسقفِ پایینِ تأخیرِ خطی‌پذیریپاسخ‌زمانی ≥ عدم قطعیتِ تأخیرِ شبکه؛ سریع‌تر ناممکن است
timestamp oracleمولدِ شناسه‌ی خطی‌پذیرِ تک‌نودیدسته‌ی پایدارشده؛ قابلِ shard نیست (TiKV/Percolator)
Lamport clockجفتِ (counter, nodeID) با قاعده‌ی max+1ترتیبِ کاملِ علّی؛ نه خطی‌پذیر، نه زمانِ فیزیکی
HLCزمانِ فیزیکی + قاعده‌ی Lamportحتی با جهشِ NTP فقط جلو می‌رود؛ CockroachDB
vector clockشمارنده به‌ازای هر نودتشخیصِ هم‌زمانی؛ اندازه‌اش با خوشه بزرگ می‌شود
consensusتوافقِ چند نود روی یک مقدار/ترتیبتصمیمِ مقدار و لاگ به هم مرتبط‌اند؛ قدرتِ عملیاتِ اتمیک و commit را با مدلِ مسئله بسنجید
total order broadcastتحویلِ همه‌ی پیام‌ها به یک ترتیب به همهمعادلِ لاگِ مشترک؛ پایه‌ی state machine replication
FLPعدمِ تضمینِ termination در مدلِ ناهمگامِ قطعیtimeout یا random مسئله را عملی می‌کند
epoch / term / ballotشماره‌ی دوره‌ای که در آن رهبر یکتاسترهبرِ epoch بالاتر برنده است؛ حلِ split brain
quorum overlapدو دورِ رأی باید عضوِ مشترک داشته باشندوگرنه نوشته‌ی تأییدشده می‌تواند گم شود
state machine replicationهمان لاگ + اجرای قطعی = وضعیتِ یکسانپایه‌ی event sourcing و تراکنشِ سریال‌پذیر
fencing tokenشماره‌ی افزایشیِ entry لاگ (zxid/revision)نوشته‌ی زامبی با شماره‌ی کوچک‌تر رد می‌شود
unclean leader electionرهبر شدنِ replicaِ عقب‌افتادهدردسترسی بیشتر، ایمنیِ کمتر (Kafka)
consensus numberحداکثرِ پیشنهاددهنده‌ای که یک primitive حل می‌کندCAS و لاگ: ∞؛ fetch-and-add: 2
coordination serviceسرویسِ قفل/lease/اعلان روی consensusZooKeeper، etcd، Consul؛ خوشه‌ی ثابتِ ۳–۵ نوده
ephemeral nodeداده‌ای که با مرگِ session حذف می‌شودتشخیصِ خرابی بدونِ انسان
observerreplicaِ بدونِ رأی در ZooKeeperخواندنِ غیرخطی‌پذیر ولی در دسترس و سریع
ConceptOne-line definitionInterview point
linearizabilityLike one copy of data with atomic operationsRecency guarantee; strongest common consistency model
registerOne object/key/row being read and writtenLinearizability's unit is one object, not a transaction
linearization pointThe imaginary instant an operation takes effect atomicallyPoints must always move forward in time
recency guaranteeA later read is at least as fresh as the previous readExactly what breaks in a “stale read”
strict serializabilitySerializability + linearizability (strong-1SR)Spanner and FoundationDB yes; CockroachDB not exactly
cross-channel raceTwo communication paths at different speedsQueue overtakes storage → permanent inconsistency
quorum (w + r > n)Reads/writes from overlapping setsNot enough for linearizability (Diagram 3)
read repairSyncing laggards before respondingMake it synchronous to linearize; Riak doesn't
CAPDuring a partition: consistency or availability“Two of three” misleads; partitions aren't a choice
PACELCPartition: A/C; else: L/CBrings normal-case latency into the equation
Attiya–WelchLower bound on linearizable latencyLatency ≥ network-delay uncertainty; faster is impossible
timestamp oracleSingle-node linearizable ID oraclePersisted batches; not shardable (TiKV/Percolator)
Lamport clock(counter, nodeID) pair with the max+1 ruleCausal total order; neither linearizable nor physical time
HLCPhysical time + the Lamport ruleOnly moves forward even across NTP jumps; CockroachDB
vector clockOne counter per nodeConcurrency detection; grows with the cluster
consensusSeveral nodes agreeing on one value/orderEquivalents: CAS, log, fetch-and-add, atomic commit
total order broadcastEvery message delivered in one order to allShared-log equivalent; basis of state machine replication
FLPNo termination guarantee in deterministic async modelsTimeouts or randomness make it practical
epoch / term / ballotThe number of the era with exactly one leaderHigher epoch wins; split-brain cure
quorum overlapThe two vote rounds must share membersOtherwise an acknowledged write can be lost
state machine replicationSame log + deterministic execution = same stateBasis of event sourcing and serializable transactions
fencing tokenRising log-entry number (zxid/revision)Zombie writes with smaller numbers are rejected
unclean leader electionA lagging replica becoming leaderMore availability, less safety (Kafka)
consensus numberMost proposers a primitive can decide amongCAS and log: ∞; fetch-and-add: 2
coordination serviceLock/lease/notification service over consensusZooKeeper, etcd, Consul; fixed 3–5 node cluster
ephemeral nodeData deleted when its session diesFailure detection without humans
observerA voteless replica in ZooKeeperNon-linearizable but available, fast reads

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

۱. linearizability دقیقاً چه چیزی را تضمین می‌کند؟1. What exactly does linearizability guarantee?

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

مثال فنی: پس از پاسخ موفق

وضعیت اولیه: SET x=1 کامل شده؛ سپس کلاینت دیگری GET x را آغاز می‌کند و نوشتن تازه‌ای وجود ندارد.

اجرای مرحله‌به‌مرحله: خواندنِ خطی‌پذیر باید ۱ ببیند؛ replicaِ عقب‌مانده که ۰ می‌دهد این قرارداد را نقض می‌کند.

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

Like one copy of data with atomic operations: once a write succeeds, every later read from any client must see the new value or newer. Concurrent (overlapping) operations may take any order, but the line of effect-points must always move forward.

۲. تفاوتِ linearizability و serializability چیست؟2. What is the difference between linearizability and serializability?

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

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

وضعیت اولیه: دو تراکنش انتقال را می‌توان به یک ترتیب serial توضیح داد.

اجرای مرحله‌به‌مرحله: اما خواندن جداگانه‌ای که بعد از پاسخ commit آغاز شده، ممکن است از replica قدیمی بیاید، اگر سیستم فقط serializability وعده داده باشد.

نتیجه و دلیل: serializability دربارهٔ تداخل تراکنش‌هاست؛ linearizability ترتیب زمان واقعیِ عملیات را می‌خواهد. ترکیب مناسب به strict serializability می‌رسد.

Linearizability is a freshness guarantee for one object; serializability is multi-object transaction isolation. So linearizability doesn't stop write skew, and serializability doesn't stop stale reads. Together they are strict serializability.

۳. چرا شرطِ w + r > n به‌تنهایی خطی‌پذیری را تضمین نمی‌کند؟3. Why isn't a quorum with w + r > n linearizable?

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

مثال فنی: Quorumِ ناقص در تاریخچه

وضعیت اولیه: n=3,w=2,r=2 است؛ نوشتن تازه فقط به A رسیده و هنوز کامل نشده است.

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

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

Because A's read can race a write and see 1 from a fresh node, while B's later-starting read hits two lagging nodes and sees 0 (Diagram 3). Linearizability needs synchronous read repair plus writers first reading the highest timestamp from a quorum; and linearizable CAS fundamentally needs consensus.

۴. خطی‌پذیری چه قیمتی دارد؟4. What does linearizability cost?

خطی‌پذیری به هماهنگیِ لازم و نسخهٔ معتبر وابسته است. این هماهنگی تأخیر دارد و هنگامِ قطعی ممکن است بخشی از درخواست‌ها نتوانند پاسخِ موفقِ مطابقِ تضمین بگیرند.

مثال فنی: بهای قطع ارتباط

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

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

نتیجه و دلیل: CAP این انتخاب هنگام partition را توصیف می‌کند؛ در حالت سالم هم هماهنگیِ دوردست هزینهٔ تأخیر دارد.

Every linearizable read/write needs a quorum round trip; the Attiya–Welch proof says latency is at least proportional to network-delay uncertainty, so it is unavoidably slow on variable networks. Even multi-core RAM isn't linearizable for the same reason — performance, not fault tolerance.

۵. CAP را در یک جمله توضیح بده و ایرادش چیست؟5. Explain CAP in one sentence, and what's wrong with it?

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

مثال فنی: بهای قطع ارتباط

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

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

نتیجه و دلیل: CAP این انتخاب هنگام partition را توصیف می‌کند؛ در حالت سالم هم هماهنگیِ دوردست هزینهٔ تأخیر دارد.

During a partition you must choose between consistency (linearizability) and availability. The flaw: “two of three” misleads because partitions are faults, not choices; the formal definition covers only linearizability and only partitions (under 8% of incidents); and some systems are neither CP nor AP. PACELC adds the normal-latency dimension.

۶. چرا نمی‌توانیم برای همه‌چیز از یک مولدِ شمارنده‌ی تک‌نودی استفاده کنیم؟6. Why can't we use one single-node counter for everything?

یک مولدِ مرکزی می‌تواند گلوگاه و نقطهٔ وابستگی باشد. برای جایگزینیِ آن هنگامِ خرابی، وضعیتِ شمارنده باید معتبر بماند؛ ترتیبِ صدور نیز الزاماً ترتیبِ commit نیست.

مثال فنی: شمارندهٔ یکتا روی یک نود

وضعیت اولیه: سرور اعداد 100,101,102 را به‌ترتیب تخصیص می‌دهد.

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

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

It is a single point of failure, slow across regions, and a bottleneck. Single-leader replication with batched ID issuance makes it tolerable (timestamp oracle). Key point: it cannot be sharded, or linearizable order dies.

۷. Lamport clock چگونه کار می‌کند و چه چیزی را تضمین نمی‌کند؟7. How does a Lamport clock work, and what doesn't it guarantee?

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

مثال فنی: Lamport روی ارسال پیام

وضعیت اولیه: شمارندهٔ A برابر ۵ است و پیام با برچسب ۶ می‌فرستد؛ B در عدد ۳ است.

اجرای مرحله‌به‌مرحله: B هنگام دریافت مقدار را به max(3,6)+1=7 می‌رساند. HLC همین ایدهٔ ترتیب را با مؤلفهٔ زمان فیزیکی ترکیب می‌کند.

نتیجه و دلیل: اگر a علت b باشد برچسب a کوچک‌تر است؛ کوچک‌تر بودن برچسب به‌تنهایی اثبات نمی‌کند a علت b بوده است.

Each node counts; every event increments; on seeing a bigger timestamp a node catches up to it. Timestamps are (counter, nodeID) pairs. Result: a causal total order — but neither linearizability (freshness) nor physical-time meaning.

۸. HLC چه چیزی را به Lamport اضافه می‌کند؟8. What does HLC add to Lamport?

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

مثال فنی: Lamport روی ارسال پیام

وضعیت اولیه: شمارندهٔ A برابر ۵ است و پیام با برچسب ۶ می‌فرستد؛ B در عدد ۳ است.

اجرای مرحله‌به‌مرحله: B هنگام دریافت مقدار را به max(3,6)+1=7 می‌رساند. HLC همین ایدهٔ ترتیب را با مؤلفهٔ زمان فیزیکی ترکیب می‌کند.

نتیجه و دلیل: اگر a علت b باشد برچسب a کوچک‌تر است؛ کوچک‌تر بودن برچسب به‌تنهایی اثبات نمی‌کند a علت b بوده است.

The physical-time dimension: it ticks like wall-clock time and jumps forward Lamport-style on bigger timestamps; each minting adds one more so backward NTP jumps can't break the order. Result: approximately-real-time timestamps with causal order, no special hardware.

۹. کی به vector clock نیاز داریم؟9. When do we need a vector clock?

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

مثال فنی: دو تصمیمِ concurrent

وضعیت اولیه: بردار رخداد اول {A:2,B:1} و دومی {A:1,B:2} است.

اجرای مرحله‌به‌مرحله: بردارها incomparable هستند و concurrent بودن آشکار می‌شود؛ یک Lamport scalar می‌تواند فقط ترتیب عددی بین آن‌ها بسازد.

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

When you must tell whether two writes were “truly concurrent” or “one before the other”. Lamport/HLC can't say from two different timestamps. A vector clock's price: one counter per node in every timestamp.

۱۰. چهار خاصیتِ consensus کدام‌اند؟10. What are the four consensus properties?

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

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

وضعیت اولیه: سه رأی‌دهنده پیشنهادهای A و B را برای رهبر دارند.

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

نتیجه و دلیل: خواص agreement، validity، integrity و termination با هم قرارداد می‌سازند؛ داشتن پیام رأی به‌تنهایی کافی نیست.

Uniform agreement (no two nodes decide differently), Integrity (the decision never changes), Validity (only a proposed value), Termination (every live node eventually decides). The first three are safety and must hold even in catastrophe; Termination is liveness and needs a live majority.

۱۱. نتیجه‌ی FLP یعنی consensus ناممکن است؟11. Does the FLP result mean consensus is impossible?

FLP محدودیتِ تضمینِ همیشگیِ پیشرفتِ اجماعِ قطعی در مدلِ کاملاً ناهمگام با احتمالِ crash را نشان می‌دهد. سامانه‌های عملی از فرض‌هایی مثل همگامیِ نهایی یا تصادفی‌سازی استفاده می‌کنند؛ timeout به‌تنهایی اثباتِ حل نیست.

مثال فنی: FLP و پیامِ نامعلومِ دیررس

وضعیت اولیه: در مدل کاملاً asynchronous، زمان رسیدن پیام کران مشخصی ندارد و یک فرایند هم ممکن است crash کند.

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

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

No; it only means termination can't always be guaranteed in a deterministic async model with crash risk. Timeouts (guessing death) or randomness make consensus practical. That is the “pure theory” vs “real system” gap.

۱۲. اجماع، لاگِ مشترک و عملیاتِ اتمیک چه ارتباط و چه تفاوتی دارند؟12. What does “consensus has many equivalent faces” mean?

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

مثال فنی: یک تصمیم با چند اسم

وضعیت اولیه: سه نود باید معلوم کنند چه کسی مالک job J7 است.

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

نتیجه و دلیل: پذیرفتن مالک متفاوت در هر نود با نام «اجماع» سازگار نیست؛ پروتکل باید یک تصمیم معتبر مشترک بدهد.

Single-value agreement, the shared log (total order broadcast), linearizable CAS, fetch-and-add and atomic commitment all convert into each other; solving one solves the rest. Only fetch-and-add is the exception (consensus number 2). In practice most systems ship a shared log.

۱۳. epoch یا term چه نقشی در جلوگیری از split brain دارد؟13. How do epochs/terms prevent split brain?

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

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

وضعیت اولیه: در گروه پنج‌نودی، A در term ۷ بوده و B با سه رأی در term ۸ انتخاب می‌شود.

اجرای مرحله‌به‌مرحله: رأی‌دهنده در هر term طبق پروتکل رأی می‌دهد و پیام term قدیمی رد می‌شود؛ انتخاب نامزد باید شرط تازگی لاگ را هم رعایت کند.

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

The algorithm doesn't guarantee “one leader at every moment” but “one leader per epoch”. A new leader arrives with a higher number and must win a quorum vote before appending; in conflict the bigger epoch wins. The two rounds' quorums must overlap.

۱۴. تفاوتِ دو دورِ رأیِ consensus با 2PC چیست؟14. How do consensus's two votes differ from 2PC?

در اجماع، رأی برای انتخابِ رهبر و تثبیتِ ورودیِ معتبر به کار می‌رود. در 2PC، شرکت‌کننده‌ها ابتدا آمادگیِ تغییرِ خود را اعلام و بعد تصمیمِ مشترکِ commit یا abort را اجرا می‌کنند.

مثال فنی: اجماع با 2PC فرق دارد

وضعیت اولیه: در consensus، سه replica از پنج replica ممکن است با خرابی دو عضو پیشرفت کنند.

اجرای مرحله‌به‌مرحله: در 2PC، participantِ prepared که تصمیم coordinator را ندارد ممکن است منتظر بماند؛ رأی yesِ همه برای مسیر commit لازم است.

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

In consensus any node may start the process and only a quorum's replies are needed; in 2PC only the coordinator collects votes and needs every participant's reply — so one silent participant suspends the whole transaction and the coordinator stays a single point of failure.

۱۵. state machine replication چیست و چرا مهم است؟15. What is state machine replication and why does it matter?

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

مثال فنی: همان لاگ، همان state

وضعیت اولیه: لاگِ commitشده شامل set(x,1) و سپس increment(x) است.

اجرای مرحله‌به‌مرحله: هر replica سالم هر دو را به همان ترتیب اعمال می‌کند و به x=2 می‌رسد.

نتیجه و دلیل: state machine باید برای همان ورودی‌ها نتیجهٔ یکسان بدهد؛ NOW() یا تصادفِ مستقل داخل apply می‌تواند replicaها را واگرا کند.

If all replicas apply the same log in the same order with deterministic logic, their states stay identical. This underpins modern single-leader replication, event sourcing, and serializable transactions (one stored procedure per entry).

۱۶. چرا رهبرِ جدید نباید داده‌ی کهنه داشته باشد؟16. Why mustn't the new leader hold stale data?

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

مثال فنی: وجود روی یک نود، commit نیست

وضعیت اولیه: رهبر entry را روی خودش نوشته، اما پیش از تأیید quorum قطع می‌شود.

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

نتیجه و دلیل: هر entryِ محلی ماندگارِ نهایی نیست؛ شرطِ commit و شرطِ انتخاب رهبر با هم از تاریخچهٔ پذیرفته‌شده محافظت می‌کنند.

A lagging new leader may overwrite entries the old leader acknowledged — breaking append-only and losing committed data. Raft only lets a node lead if its log is at least as fresh as a majority's. Kafka's unclean leader election drops exactly this guarantee for availability.

۱۷. در خواندنِ خطی‌پذیر چگونه مطمئن می‌شویم رهبر هنوز معتبر است؟17. Why vote even for a linearizable read?

پیش از خواندنِ قوی باید مطمئن شویم رهبر هنوز مجاز است و وضعیتِ لازم اعمال شده است. تماس با اکثریت یا lease با فرض‌های معتبر از راه‌های این کارند؛ رأیِ جدا برای هر خواندن قانونِ عمومی نیست.

مثال فنی: رهبرِ هنوز معتبر؟

وضعیت اولیه: A قبلاً رهبر بوده، اما از اکثریت جدا شده و B رهبر تازه شده است.

اجرای مرحله‌به‌مرحله: خواندن محلیِ A می‌تواند قدیمی باشد؛ پروتکل باید اعتبار رهبری و رسیدن state به نقطهٔ لازم را پیش از پاسخ خطی‌پذیر بررسی کند.

نتیجه و دلیل: single-leader بودن اسمِ معماری است؛ خواندن از هر رهبرِ سابق بدون کنترل اعتبار خطی‌پذیر نمی‌شود.

Because a node that thinks it is leader may no longer be one (a new election happened). To be sure, reads like writes need quorum confirmation of “still leader” — etcd's mechanism for linearizable reads. ZooKeeper has linearizable writes but its reads can be stale.

۱۸. چرا consensus را همه‌جا به کار نمی‌بریم؟18. Why not use consensus everywhere?

اجماع پیام، ذخیره و هماهنگی می‌خواهد و پیشرفت به اعضای کافی وابسته است. بهتر است آن را برای تصمیم‌هایی مصرف کنیم که اختلافشان قابل‌قبول نیست.

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

وضعیت اولیه: گروه سه‌عضوی برای عملیات نیازمند اکثریت به دو عضو دسترس‌پذیر نیاز دارد.

اجرای مرحله‌به‌مرحله: خرابی یک عضو قابل‌تحمل است؛ با خرابی دو عضو پیشرفت متوقف می‌شود تا اکثریت برگردد.

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

It is expensive: a strict majority (3 nodes per failure), every operation talking to a quorum, more nodes = slower, and only the majority side advancing in partitions. Timeout tuning on variable networks is hard and Raft is flaky-link sensitive (hence pre-vote). Where strong guarantees are unneeded, weaker models are faster and more available.

۱۹. سرویسِ هماهنگی دقیقاً چه چیزی روی consensus می‌سازد؟19. What exactly does a coordination service build on consensus?

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

مثال فنی: قفل و تنظیمات روی etcd

وضعیت اولیه: کلید /jobs/J7/owner و نسخهٔ پیکربندی در سرویس هماهنگی نگهداری می‌شوند.

اجرای مرحله‌به‌مرحله: عملیات شرطی مالک را تعیین می‌کند و watch تغییر را اطلاع می‌دهد؛ مصرف‌کننده بعد از قطع اتصال باید وضعیت را دوباره همگام کند.

نتیجه و دلیل: watch خبر تغییر است و خودش مجوز اثر نیست؛ کارگر همچنان به lease و fencing متناسب با منبع کار نیاز دارد.

Locks and leases (linearizable CAS), fencing tokens (rising log numbers like zxid/revision), failure detection (sessions + ephemeral nodes) and change notifications (watches). Detection and notification need no consensus, but beside atomic ops they are hugely useful. Big win: you outsource consensus to a fixed 3–5 nodes, not the whole system.

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

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

مثال فنی: تضمین را برای هر مسیر انتخاب کن

وضعیت اولیه: ثبت نام کاربری نیاز به یکتایی فوری دارد، اما شمارندهٔ بازدید می‌تواند چند ثانیه عقب باشد.

اجرای مرحله‌به‌مرحله: مسیر اول هماهنگی و قید سخت می‌گیرد؛ مسیر دوم جمع‌زدن غیرهم‌زمان با کنترل تکرار دارد.

نتیجه و دلیل: تضمین قوی را بر اساس قانون همان عملیات انتخاب می‌کنیم؛ یک برچسب consistency برای همهٔ بخش‌های محصول کافی نیست.

“Linearizability means the system behaves like one copy of data; fault tolerance needs consensus — a shared log with quorum votes; and where strong guarantees are unneeded, use logical clocks and weaker models instead of paying the quorum price.”

۶.۳. اشتباهات رایج در جواب‌های مصاحبه6.3. Common mistakes in interview answers

  • ❌ «linearizability همان serializability است.» → ✅ یکی تضمینِ تازگیِ یک شیء است، دیگری ایزوله‌سازیِ تراکنشِ چندشیء‌ای.
  • ❌ «با quorum (w + r > n) سیستم قویاً سازگار است.» → ✅ خیر؛ حتی با quorum هم خواندنِ کهنه ممکن است (نمودار ۳).
  • ❌ «CAP یعنی سه تا، دوتا.» → ✅ partition انتخابی نیست؛ فقط هنگامِ partition باید بینِ C و A انتخاب کنی.
  • ❌ «از UUID استفاده می‌کنیم، پس ترتیب هم داریم.» → ✅ UUID v4 ترتیبِ تصادفی دارد؛ برای ترتیب باید ULID/UUIDv7 یا ساعتِ منطقی بگذاری.
  • ❌ «timestamp سرور کافی است؛ NTP که داریم.» → ✅ skew و جهشِ ساعت ترتیب را می‌شکند؛ wall-clock خطی‌پذیر نیست.
  • ❌ «الگوریتمِ consensus داریم، پس همه‌ی عملیات‌هایمان خطی‌پذیرند.» → ✅ readِ بدونِ بررسیِ رهبری می‌تواند کهنه باشد.
  • ❌ «FLP ثابت کرد consensus ممکن نیست.» → ✅ فقط termination در مدلِ ناهمگامِ قطعی تضمین نمی‌شود؛ با timeout/random حل است.
  • ❌ «برای سرعت، رهبرِ عقب‌افتاده هم می‌تواند رهبر شود.» → ✅ یعنی کنارگذاشتنِ ایمنی؛ ممکن است نوشته‌ی commitشده گم شود.
  • ❌ “Linearizability is serializability.” → ✅ One is single-object freshness, the other multi-object transaction isolation.
  • ❌ “With a quorum (w + r > n) the system is strongly consistent.” → ✅ No; stale reads stay possible even with quorums (Diagram 3).
  • ❌ “CAP means two of three.” → ✅ Partitions aren't a choice; only during partitions must you pick C or A.
  • ❌ “We use UUIDs, so we have order.” → ✅ UUID v4 is randomly ordered; order needs ULID/UUIDv7 or a logical clock.
  • ❌ “Server timestamps suffice; we have NTP.” → ✅ Skew and clock jumps break order; wall-clock isn't linearizable.
  • ❌ “We run consensus, so all our operations are linearizable.” → ✅ Reads that never verify leadership can be stale.
  • ❌ “FLP proved consensus impossible.” → ✅ Only termination in deterministic async models is unguaranteed; timeouts/randomness fix it.
  • ❌ “For speed, a lagging replica may lead.” → ✅ That drops safety; committed writes may be lost.

۶.۴. چک‌لیستِ ۵ دقیقه‌ای قبل از مصاحبه6.4. Five-minute checklist before the interview

  1. تعریفِ linearizability + قاعده‌ی «خواندنِ بعدی هرگز کهنه‌تر نشود» را مرور کن.
  2. تفاوتِ linearizability و serializability و ترکیبشان (strict serializability) را بگو.
  3. سه جایی که خطی‌پذیری الزامی است: قفل/انتخابِ رهبر، قیدِ uniqueness سخت، کانال‌های موازی.
  4. چرا quorum کافی نیست و دو راهِ تعمیرش (read repair هم‌زمان + خواندنِ timestamp قبل از نوشتن).
  5. CAP را درست بیان کن: «هنگامِ partition، سازگار یا در دسترس» + PACELC.
  6. سه ساعت: Lamport، HLC، vector — هر کدام چه تضمینی می‌دهند.
  7. چهار خاصیتِ consensus و اینکه کدام‌ها safety و کدام liveness است.
  8. یک جمله درباره‌ی epoch/term و یک جمله درباره‌ی state machine replication.
  1. Review linearizability's definition + the “later reads never older” rule.
  2. State linearizability vs serializability and their combination (strict serializability).
  3. Three places linearizability is mandatory: locks/leader election, hard uniqueness, parallel channels.
  4. Why quorums don't suffice and the two fixes (synchronous read repair + pre-write timestamp reads).
  5. State CAP correctly: “during partitions, consistent or available” + PACELC.
  6. The three clocks: Lamport, HLC, vector — each guarantees what.
  7. Consensus's four properties, and which are safety vs liveness.
  8. One sentence on epoch/term, one on state machine replication.

۷7 تمرین‌های مصاحبه و آزمایشگاه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

تمرین ۱ — ساعتِ Lamport عملیExercise 1 — a working Lamport clock

L1 · پایهL1 · Foundationsوابسته به نقشRole-specific25 دقیقه بحث25 min discussion

Lamport پایهٔ مفید است ولی پیاده‌سازی سه پروسه برای backend عمومی اولویت نخست نیست.Useful distributed-systems foundations, with lower general-backend priority.

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

برای سه process، send/receive و رخداد محلی را با Lamport clock شماره‌گذاری کن. با tie-breaker process ID ترتیب کامل بساز. یک مثال نشان بده که L(a)<L(b) علت‌ومعلولی بودن را اثبات نمی‌کند؛ vector clock و هزینه‌اش را مقایسه کن.Label local events and messages using Lamport clocks and process-ID tie breaking. Show why increasing timestamps do not imply causality or detect concurrency; compare vector-clock costs.

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

  • receive=max(local,received)+1 درست استCorrect receive rule
  • ترتیب کل را با causality یکی نمی‌گیردTotal order versus causality
  • concurrency از Lamport قابل تشخیص نیستConcurrency limitations

پیگیری مصاحبه:Interview follow-up: دو رخداد بدون هیچ پیام چه timestampهایی می‌توانند داشته باشند؟What timestamps can two independent events with no messages have?

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

سه پروسه‌ی ساده بنویس که پیام رد و بدل می‌کنند و هر کدام ساعتِ Lamport خودشان را دارند. قاعده‌ی «افزایش هنگامِ رویداد» و «رساندن به max هنگامِ دریافت» را پیاده‌سازی و تست کن. بعد سناریوی نمودار ۵ را اجرا کن و نشان بده ترتیبِ نهاییِ timestampها دقیقاً (1,A) < (1,C) < (2,B) می‌شود. سوال: چه ورودی‌ای می‌تواند نشان دهد که این ساعت، «هم‌زمانی» را تشخیص نمی‌دهد؟

Write three small processes that exchange messages, each keeping its own Lamport clock. Implement “increment on event” and “catch up to max on receive” and test them. Then run Diagram 5's scenario and show the final timestamp order is exactly (1,A) < (1,C) < (2,B). Question: what input could show this clock cannot detect “concurrency”?

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

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Lamport · Time, Clocks, and Ordering · 1978 · Atlassian · engineering interview handbook

تمرین ۲ — quorum و نقض خطی‌پذیریExercise 2 — quorums and a linearizability violation

L3 · Staff / PrincipalL3 · Staff / Principalوابسته به نقشRole-specific45 دقیقه بحث45 min discussion

write با هدف w=3 هنوز کامل نشده و فقط یک replica مقدار تازه را دارد؛ read باید پیش از پاسخ، بیشینهٔ نسخه را به quorum برگرداند.The write targeting w=3 is incomplete with one updated replica; reads must write the maximum version back to a quorum before replying.

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

با سه replica و write در جریان که هنوز فقط به یکی رسیده، دو quorum read بساز که اولی جدید و دومی قدیمی می‌خواند. سپس الگوریتم register با version مرتب و write-back به اکثریت پیش از بازگشت read را مشخص کن. برای multi-writer مرحلهٔ query پیش از write و فرض membership ثابت را تحلیل کن.Construct new-then-old quorum reads during an incomplete three-replica write. Specify ordered versions and majority write-back before returning a read. Discuss a query phase for multiple writers, fixed membership and crash assumptions.

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

  • w=3 تأییدشده روی همه را با partial write یکی نمی‌گیردPartial versus acknowledged writes
  • background repair را با synchronous quorum write-back جدا می‌کندBackground repair versus quorum write-back
  • version، crash و concurrent writer assumptions معلوم‌اندVersion and writer assumptions

پیگیری مصاحبه:Interview follow-up: اگر یک reader بعد از خواندن جدید و قبل از write-back بمیرد، کدام read باید completed حساب شود؟A reader crashes after observing the new value but before write-back. Should that read count as completed?

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

سه replica ابتدا v1 دارند. write تک‌نویسنده با هدف w=3 فقط n1 را به v2 رسانده و هنوز کامل نشده است. read اول r=2 از {n1,n2} مقدار v2 و read دوم که بعد از پایان اول آغاز می‌شود از {n2,n3} مقدار v1 می‌گیرد؛ history را رسم و نقض new-then-old را ثابت کن. نسخهٔ اصلاح‌شده، max نسخه را پیش از پاسخ read به quorum دوگرهی write-back می‌کند و replica هرگز به نسخهٔ کمتر عقب نمی‌رود. قبل و بعد از write-back crash تزریق کن و completed را از دید caller تعریف کن. read repair پس‌زمینه این شرط را نمی‌دهد. مدل SWMR با عضویت ثابت را صریح بنویس؛ برای MWMR query timestamp قبل از write، tag یکتای مرتب و quorum مناسب لازم است.

Start three replicas at v1. An incomplete single-writer write targeting w=3 has updated only n1 to v2. Read1 from {n1,n2} returns v2; Read2 starts after Read1 completes and returns v1 from {n2,n3}. Draw the history and prove new-then-old violates linearizability. Fix it with maximum-version write-back to a two-node quorum before returning, with replicas rejecting version regression. Inject crashes before and after write-back; completion is observed by the caller. Background repair does not satisfy the condition. State fixed-membership SWMR assumptions; MWMR additionally needs pre-write timestamp queries, uniquely ordered tags and suitable quorums.

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

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Attiya, Bar-Noy, Dolev · atomic registers · 1995 · Jepsen · consistency models

تمرین ۳ — مولدِ شناسه‌ی oracle با failoverExercise 3 — an oracle ID generator with failover

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

رزرو بادوامِ بازه پیش از صدور، یکتایی را با پذیرش gap حفظ می‌کند؛ شاردها می‌توانند بازه یا namespace جدا داشته باشند.Durably reserving ranges before issuance preserves uniqueness with gaps; shards may allocate disjoint ranges or namespaces.

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

مولد ID با رزرو پایدار بازه طراحی کن و crash وسط دسته را تحلیل کن. uniqueness، gap-free، ترتیب تخصیص و ترتیب مشاهدهٔ real-time را جدا تعریف کن. طرح مرکزی، UUID و shard-prefixed ID را مقایسه کن؛ برای failover، allocator را fence کن.Persist ID-range reservations before issuing IDs and crash mid-range. Separate uniqueness, gaplessness, allocation order and real-time observation. Compare a centralized allocator, UUIDs and shard-prefixed IDs; fence failover.

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

  • high-water mark قبل از صدور ID پایدار می‌شودDurable high-water mark
  • gap را از duplicate جدا می‌کندGaps versus duplicate IDs
  • نیاز به هماهنگی را به ضمانت ordering وصل می‌کندOrdering-specific coordination

پیگیری مصاحبه:Interview follow-up: دو node بازه‌های جدا دارند؛ آیا IDهای صادرشده به ترتیب زمان جهانی افزایش می‌یابند؟Two nodes issue IDs from disjoint ranges. Do the IDs increase in global real-time order?

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

مولد شناسه، high-water mark دسته را قبل از تحویل اولین ID پایدار می‌کند. دستهٔ ۱ تا ۵ را رزرو کن، ۱ تا ۳ را بده، crash و از ۶ شروع کن؛ نبود تکرار و مجازبودن gap را assert کن. سپس دو allocator با بازهٔ بادوام جدا یا namespace جدا بساز و یکتایی را بیازما. uniqueness، gap-free، ترتیب تخصیص و ترتیب real-time پاسخ‌های غیرهم‌پوشان را جدا تعریف کن؛ batching و چند allocator ممکن است ترتیب جهانی را از بین ببرند. در failover هیچ بازهٔ رزروشده را دوباره واگذار نکن و reserve بعدی را با epoch در storage محافظت کن. fence روی reserve به‌تنهایی پاسخ‌های باقی‌مانده در RAM رهبر قدیم را متوقف نمی‌کند؛ برای ترتیب جهانی، مسیر پاسخ نیز باید مالک معتبر را کنترل کند.

Persist each batch high-water mark before issuing its first ID. Reserve 1..5, issue 1..3, crash and restart at 6; assert no duplicates with allowed gaps. Then implement two allocators with durably disjoint ranges or namespaces and test uniqueness. Define uniqueness, gap-free allocation, allocation order and real-time ordering of non-overlapping responses separately; batching across allocators can break global order. Never reassign reserved ranges after failover, and epoch-fence future reservations at storage. A reserve fence alone cannot stop an old leader serving its remaining RAM IDs; global ordering also needs a valid-owner check on the response path.

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

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Amazon · SDE III interview preparation · Atlassian · engineering interview handbook

تمرین ۴ — خطی‌سنجِ سادهExercise 4 — a mini linearizability checker

L3 · Staff / PrincipalL3 · Staff / Principalوابسته به نقشRole-specific40 دقیقه بحث40 min discussion

linearizability checker برای infra/database بسیار خوب ولی در جلسه معمول بیشتر history reasoning پرسیدنی است.A specialist checker lab; small-history reasoning is the live interview version.

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

یک history کوچک شامل invoke/return read و write را روی register خطی‌سنجی کن. ترتیب real-time را حفظ و عملیات هم‌پوشان را جابه‌جا کن. برای test harness bounded search طراحی کن؛ pending operation و timeout نامعلوم را درست مدل کن.Check a small register history while preserving real-time precedence and reordering overlapping operations. Design bounded search, model pending/indeterminate operations and explain the limits of sampled histories.

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

  • completion قبل از invocation بعدی تقدم اجباری داردReal-time precedence
  • خواندن stale بعد از write تمام‌شده رد می‌شودCompleted-write stale-read rejection
  • checker اثبات همهٔ اجراهای آینده نیستLimits of finite testing

پیگیری مصاحبه:Interview follow-up: timeout یک write را failed حساب می‌کنی یا indeterminate؟ چرا؟Do you model a timed-out write as failed or indeterminate, and why?

آزمایشگاه تکمیلی · زمان تقریبی ۴–۸ ساعتExtended hands-on laboratory · estimated 4–8 hours

یک key-value store ساده بساز و یک تست‌کننده که زمانِ شروع و پایانِ هر read/write را ثبت کند. بعد الگوریتمی بنویس که بررسی کند آیا می‌توان این عملیات‌ها را در یک ترتیبِ معتبرِ register چید (هر read مقدارِ آخرین write قبل از خودش را برگرداند) طوری که ترتیبِ زمانِ واقعی هم رعایت شود. سوال: با اجرای این خطی‌سنج روی یک پیاده‌سازیِ «خواندن از replica»، کدام اجراها رد می‌شوند؟

Build a tiny key-value store plus a tester recording each read/write's start and end time. Then write an algorithm checking whether these operations fit a valid register order (each read returning the latest write before it) that also respects real-time order. Question: running this checker on a “read from any replica” implementation, which executions fail?

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

ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Jepsen · consistency models · Attiya, Bar-Noy, Dolev · atomic registers · 1995

نکته‌ی پایانیClosing note
این فصل اوجِ نظریه‌ی فصل‌های ۶ تا ۹ است: replication که با تازگیِ خطی‌پذیر مهار شده باشد، به‌کمکِ consensus که روی یک لاگِ مشترک سوار است، سیستمی می‌سازد که با وجودِ همه‌ی خرابی‌های فصل ۹ همچنان «مثلِ یک ماشینِ درست» رفتار می‌کند. و البته یادت باشد: consensus ابزارِ همه‌کاره نیست — وقتی تازگیِ قطعی لازم نیست، مدل‌های ضعیف‌ترِ فصل‌های ۶ و ۱۳ سریع‌تر و در دسترس‌ترند.
This chapter is the theoretical peak of Chapters 6–9: replication tamed by linearizable freshness, carried by consensus on a shared log, builds a system that still behaves “like one correct machine” despite all of Chapter 9's failures. And remember: consensus is no silver bullet — where firm freshness is unneeded, the weaker models of Chapters 6 and 13 are faster and more available.