ЁЯПл The SchoolтА║ЁЯУР System DesignтА║ЁЯФБ рдзрдбрд╛ 10 тАФ Consistency: рд╕реВрдЪрдирд╛-рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рддреАрди рдкреНрд░рддреА
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФБ рдзрдбрд╛ 10 тАФ Consistency: рд╕реВрдЪрдирд╛-рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рддреАрди рдкреНрд░рддреА

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдкреБрдвреАрд▓: lesson-11-distributed-transactions


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдд рдЙрднрд╛ рдХрд░рдгрд╛рд░рд╛ рдкреНрд░рд╢реНрди: data рдПрдХрд╛рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдард┐рдХрд╛рдгреА рд░рд╛рд╣рддреЛ рддреЗрд╡реНрд╣рд╛ рдХреЛрдгрддреА рдкреНрд░рдд рдЦрд░реЗ рд╕рд╛рдВрдЧрддреЗ? рддреБрдореНрд╣реА рд╢рд┐рдХрддрд╛ strong рдЖрдгрд┐ eventual consistency, replicas рдЖрдгрд┐ replication lag, quorums (R + W > N), read-your-writes, monotonic reads, рд╕реЛрдкреНрдпрд╛ рд╢рдмреНрджрд╛рдВрдд CAP рдЖрдгрд┐ PACELC, рдЖрдгрд┐ data рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░рд╛рд╕рд╛рдареА рдирд┐рд╡рдб рдХрд╢реА рдХрд░рд╛рдпрдЪреА.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рджреАрдкрд┐рдХрд╛рдЪреЗ рдирд┐рдпреЛрдЬрди рдХрд╛рд░реНрдпрд╛рд▓рдп рд╕реВрдЪрдирд╛-рдиреЛрдВрджрд╡рд╣реА рддреАрди рд╢рд┐рдХреНрд╖рдХ-рдЦреЛрд▓реНрдпрд╛рдВрдд рдареЗрд╡рддреЗ, рдореНрд╣рдгрдЬреЗ рдПрдХ рдЦреЛрд▓реА рдХреБрд▓реВрдкрдмрдВрдж рдЕрд╕рд▓реА рддрд░реА рд╕рдЧрд│реНрдпрд╛рдВрдЪреЗ рдХрд╛рдо рдерд╛рдВрдмрдд рдирд╛рд╣реА.

рдХрддрд░рд┐рдирд╛ рдПрдХ рд╕реВрдЪрдирд╛ рдмрджрд▓рддреЗ: "рдкрд░реАрдХреНрд╖рд╛ рдордВрдЧрд│рд╡рд╛рд░реА рд╣рд▓рд╡рд▓реА." рддреА рд╣реЗ рдЦреЛрд▓реА 1 рдордзреАрд▓ рдиреЛрдВрджрд╡рд╣реАрдд рд▓рд┐рд╣рд┐рддреЗ. рдордЧ рдПрдХ рдзрд╛рд╡рдкрдЯреВ рд╣рд╛ рдмрджрд▓ рдЦреЛрд▓реА 2 рдЖрдгрд┐ 3 рдордзреНрдпреЗ рдШреЗрдКрди рдЬрд╛рддреЛ. рдзрд╛рд╡рдкрдЯреВрд▓рд╛ рдереЛрдбрд╛ рд╡реЗрд│ рд▓рд╛рдЧрддреЛ.

рдРрд╢реНрд╡рд░реНрдпрд╛ рдЦреЛрд▓реА 3 рдордзреНрдпреЗ рдЖрд╣реЗ. рддреА рдзрд╛рд╡рдкрдЯреВ рдкреЛрд╣реЛрдЪрдгреНрдпрд╛рдЖрдзреА рдиреЛрдВрджрд╡рд╣реА рд╡рд╛рдЪрддреЗ. рддрд┐рд▓рд╛ "рд╕реЛрдорд╡рд╛рд░" рджрд┐рд╕рддреЛ. рдХрд╛рд╣реАрд╣реА рддреБрдЯрд▓реЗрд▓реЗ рдирд╛рд╣реА. рддрд┐рдЪреА рдкреНрд░рдд рдлрдХреНрдд рдЙрд╢рд┐рд░рд╛рдЪреА рдЖрд╣реЗ.

рджреАрдкрд┐рдХрд╛ рдПрдХ рдирд┐рдпрдо рдХрд░рддреЗ: "рдЭрд╛рд▓реЗ рдореНрд╣рдгрдгреНрдпрд╛рдЖрдзреА рдмрджрд▓ рджреЛрди рдиреЛрдВрджрд╡рд╣реНрдпрд╛рдВрдд рд▓рд┐рд╣рд╛. рджреЛрди рдиреЛрдВрджрд╡рд╣реНрдпрд╛ рд╡рд╛рдЪрд╛ рдЖрдгрд┐ рдЬрд╛рд╕реНрдд рдкрд╛рди-рдХреНрд░рдорд╛рдВрдХ рдЕрд╕рд▓реЗрд▓реЗ рдкрд╛рди рдареЗрд╡рд╛." рджреЛрди рд▓рд┐рд╣рд┐рд▓реЗрд▓реНрдпрд╛ рдЕрдзрд┐рдХ рджреЛрди рд╡рд╛рдЪрд▓реЗрд▓реНрдпрд╛ рдиреЛрдВрджрд╡рд╣реНрдпрд╛ рдореНрд╣рдгрдЬреЗ рдЪрд╛рд░ тАФ рддреАрди рдЦреЛрд▓реНрдпрд╛рдВрдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд. рдореНрд╣рдгреВрди рджреЛрдиреНрд╣реА рдЧрдЯрд╛рдВрдд рдиреЗрд╣рдореА рдПрдХ рдЦреЛрд▓реА рд╕рд╛рдорд╛рдИрдХ рдЕрд╕рддреЗ, рдЖрдгрд┐ рд╡рд╛рдЪрдгрд╛рд░реАрд▓рд╛ рдиреЗрд╣рдореА рдирд╡реЗ рдкрд╛рди рд╕рд╛рдкрдбрддреЗ.

рдПрдХреЗ рджрд┐рд╡рд╢реА рдорд╛рд░реНрдЧрд┐рдХрд╛ рдЕрдбрддреЗ. рдЦреЛрд▓реА 3 рдмрд╛рдХреАрдЪреНрдпрд╛рдВрдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪреВ рд╢рдХрдд рдирд╛рд╣реА. рдПрдХ рдкрд╛рд▓рдХ рдЦреЛрд▓реА 3 рд▓рд╛ рд╡рд┐рдЪрд╛рд░рддреЛ. рдЦреЛрд▓реА 3 рдХрдбреЗ рджреЛрди рдкрд░реНрдпрд╛рдп рдЕрд╕рддрд╛рдд: рдЬреЗ рдЖрд╣реЗ рддреЗ рдЙрддреНрддрд░ рджреНрдпрд╛рдпрдЪреЗ (рдХрджрд╛рдЪрд┐рдд рдЬреБрдиреЗ), рдХрд┐рдВрд╡рд╛ "рдХреГрдкрдпрд╛ рдерд╛рдВрдмрд╛" рдореНрд╣рдгрд╛рдпрдЪреЗ (рдмрд░реЛрдмрд░ рд░рд╛рд╣рд╛рдпрдЪреЗ). рджреЛрдиреНрд╣реА рдПрдХрджрдо рдХрд░рддрд╛ рдпреЗрдд рдирд╛рд╣реА. рд╣реАрдЪ рдирд┐рд╡рдб рдореНрд╣рдгрдЬреЗ CAP.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    w["тЬНя╕П Katrina writes v8<br/>exam moved to Tuesday"]
    subgraph n3["ЁЯУЪ N = 3 copies"]
      c1["room 1 ┬╖ v8"]
      c2["room 2 ┬╖ v8"]
      c3["room 3 ┬╖ v7 ┬╖ lag"]
    end
    w -->|"W = 2: wait for 2 copies"| c1
    w -->|"W = 2"| c2
    w -.->|"later"| c3
    r["ЁЯСА Aishwarya reads<br/>R = 2, keep the newest"]
    r --> c2
    r --> c3
    ok["2 + 2 > 3 тЖТ the groups overlap<br/>she sees v8"]
    r --> ok

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/system-design/lesson-diagrams.html#l10

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдзрдбреЗ 05тАУ09 рдиреЗ рд╕рдЧрд│реАрдХрдбреЗ рдкреНрд░рддреА рддрдпрд╛рд░ рдХреЗрд▓реНрдпрд╛: caches, read replicas, рдЗрддрд░ services update рдХрд░рдгрд╛рд░реЗ events. рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдд рдЙрд╢рд┐рд░рд╛рдЪреА рдЕрд╕реВ рд╢рдХрддреЗ. рдирд┐рдпреЛрдЬрди рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдиреЗ data рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░рд╛рд╕рд╛рдареА рдард░рд╡рд▓реЗ рдкрд╛рд╣рд┐рдЬреЗ рдХреА рдХрд┐рддреА рдЙрд╢реАрд░ рдЪрд╛рд▓рддреЛ. рдПрдХрд╛ рджрд┐рд╢реЗрдиреЗ рдЪреВрдХ рдЭрд╛рд▓реА, рддрд░ рдкрд╛рд▓рдХ fee рджреЛрдирджрд╛ рднрд░рддреЛ. рджреБрд╕рд▒реНрдпрд╛ рджрд┐рд╢реЗрдиреЗ рдЪреВрдХ рдЭрд╛рд▓реА, рддрд░ рдкреВрд░реНрдг app рдПрдХрд╛ view counter рд╕рд╛рдареА рдерд╛рдВрдмрддреЛ.

рд╕реВрдЪрдирд╛ рдлрд▓рдХ, рдкреНрд░рддреНрдпреЗрдХ data рдкреНрд░рдХрд╛рд░рд╛рд╕рд╛рдареА рдард░рд╡рд▓реЗрд▓реЗ:

data рдирд┐рд╡рдб рдХрд╛
fee payments strong рдкреИрд╕реЗ рдХрдзреАрд╣реА рджреЛрдирджрд╛ рдореЛрдЬрд▓реЗ рдЬрд╛рдК рдирдпреЗрдд рдХрд┐рдВрд╡рд╛ рд╣рд░рд╡реВ рдирдпреЗрдд
рдЧреБрдг strong рджреБрд░реБрд╕реНрддреАрдирдВрддрд░ рдкрд╛рд▓рдХрд╛рд▓рд╛ рдЬреБрдирд╛ рдЧреБрдг рджрд┐рд╕рддрд╛ рдХрд╛рдорд╛ рдирдпреЗ
рдХреЛрдг рдХреЛрдгрддреНрдпрд╛ рд╡рд░реНрдЧрд╛рдд рдЖрд╣реЗ (access) strong рдХрд╛рдврд▓реЗрд▓реНрдпрд╛ рдкрд╛рд▓рдХрд╛рдЪрд╛ access рд▓рдЧреЗрдЪ рдЧреЗрд▓рд╛ рдкрд╛рд╣рд┐рдЬреЗ
рд╕реВрдЪрдирд╛, рддрд┐рдЪреНрдпрд╛ рд▓реЗрдЦрд┐рдХреЗрд╕рд╛рдареА read-your-writes "posted" рдирдВрддрд░ рдХрддрд░рд┐рдирд╛рд▓рд╛ рддрд┐рдЪреА рд╕реНрд╡рддрдГрдЪреА post рджрд┐рд╕рд▓реА рдкрд╛рд╣рд┐рдЬреЗ
рд╕реВрдЪрдирд╛, рдЗрддрд░ рдкрд╛рд▓рдХрд╛рдВрд╕рд╛рдареА eventual (seconds) рдХрд╛рд╣реА seconds рдЙрд╢реАрд░ рдЪрд╛рд▓рддреЛ
"312 рдкрд╛рд▓рдХрд╛рдВрдиреА рд╡рд╛рдЪрд▓реА" counters, likes eventual 2 seconds рдЬреБрдиреНрдпрд╛ рдЖрдХрдбреНрдпрд╛рдиреЗ рдХреЛрдгрд╛рдЪреЗ рдиреБрдХрд╕рд╛рди рд╣реЛрдд рдирд╛рд╣реА

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

design/blocks.py рдордзреАрд▓ quorum_ok(n, r, w) r + w > n рдкрд░рдд рдХрд░рддреЛ. quorum_read(copies, r) (version, value) рдкреНрд░рддреАрдВрдЪреА рдпрд╛рджреА рдШреЗрддреЛ, рддреНрдпрд╛рдВрдкреИрдХреА рдкрд╣рд┐рд▓реНрдпрд╛ r рд╡рд╛рдЪрддреЛ рдЖрдгрд┐ рд╕рд░реНрд╡рд╛рдд рдореЛрдареА version рдареЗрд╡рддреЛ. design/demo.py рдордзреАрд▓ consistency() N = 3 рд╕рд╛рдареА рдЪрд╛рд░ R/W рдЬреЛрдбреНрдпрд╛ рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рддреЛ, рдордЧ рдЕрд╢рд╛ рддреАрди рдкреНрд░рддреА рд╡рд╛рдЪрддреЛ рдЬреНрдпрд╛рдВрдкреИрдХреА рдлрдХреНрдд рдПрдХреАрдХрдбреЗ рдирд╡реА version 8 рдЖрд╣реЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 design/demo.py consistency
python3 - <<'EOF'
import sys; sys.path.insert(0, "design"); from blocks import quorum_ok, quorum_read
for n in (3, 5):
    strong = [f"R={r} W={w}" for r in range(1, n + 1) for w in range(1, n + 1) if quorum_ok(n, r, w)]
    print(f"N={n}: {len(strong)} of {n * n} choices are strong ┬╖ smallest pair with R = W: R=W={n // 2 + 1}")
copies = [(8, "exam moved to Tuesday"), (7, "exam on Monday"), (7, "exam on Monday")]
print("W=1 (only copy 1 has v8):", "R=1 from copy 3 тЖТ", quorum_read(copies[2:], 1)[1], "┬╖ R=2 from copies 2,3 тЖТ", quorum_read(copies[1:], 2)[1])
copies = [(8, "exam moved to Tuesday"), (8, "exam moved to Tuesday"), (7, "exam on Monday")]
print("W=2 (copies 1,2 have v8):", "R=2 from copies 2,3 тЖТ", quorum_read(copies[1:], 2)[1], "┬╖ R=2 from copies 1,3 тЖТ", quorum_read(copies[::2], 2)[1])
EOF
python3 design/test_design.py

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

consistency рд╣реЗ рдЫрд╛рдкрддреЛ:

тФАтФА 3 copies (N=3): which read/write sizes are sure to see the latest write? (R + W > N)
   R=1 W=1 тЖТ eventual: may read a stale copy
   R=1 W=3 тЖТ strong: overlaps
   R=2 W=2 тЖТ strong: overlaps
   R=3 W=1 тЖТ strong: overlaps
   copies [(7, 'exam on Monday'), (8, 'exam moved to Tuesday'), (7, 'exam on Monday')] ┬╖ read R=1 тЖТ 'exam on Monday' ┬╖ read R=2 тЖТ 'exam moved to Tuesday'
   CAP: during a network split you choose тАФ answer (maybe stale) or refuse (stay correct)
   most apps: strong for money and grades, eventual for likes and view counts

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

N=3: 6 of 9 choices are strong ┬╖ smallest pair with R = W: R=W=2
N=5: 15 of 25 choices are strong ┬╖ smallest pair with R = W: R=W=3
W=1 (only copy 1 has v8): R=1 from copy 3 тЖТ exam on Monday ┬╖ R=2 from copies 2,3 тЖТ exam on Monday
W=2 (copies 1,2 have v8): R=2 from copies 2,3 тЖТ exam moved to Tuesday ┬╖ R=2 from copies 1,3 тЖТ exam moved to Tuesday

Tests рдордзреНрдпреЗ тЬЕ L10 R + W > N overlaps; R=1 W=1 does not рдЕрд╕рддреЗ рдЖрдгрд┐ рд╢реЗрд╡рдЯреА 18/18 passed рдпреЗрддреЗ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

3 рдкреНрд░рддреА рдЕрд╕рддрд╛рдирд╛ 9 рдкреИрдХреА 6 R/W рдЬреЛрдбреНрдпрд╛ strong рдЕрд╕рддрд╛рдд; 5 рдкреНрд░рддреА рдЕрд╕рддрд╛рдирд╛ рд╕рд░реНрд╡рд╛рдд рд▓рд╣рд╛рди рд╕рдорд╛рди рдЬреЛрдбреА R = W = 3 тАФ рдореНрд╣рдгрдЬреЗ рдмрд╣реБрдордд. Demo рдордзреАрд▓ R = 2 read рдиреАрдЯ рдкрд╛рд╣рд╛: рддреНрдпрд╛рд▓рд╛ "Tuesday" рд╕рд╛рдкрдбрд▓реЗ, рдкрдг рдлрдХреНрдд рдПрдХрд╛рдЪ рдкреНрд░рддреАрдХрдбреЗ version 8 рд╣реЛрддреА (W = 1), рдЖрдгрд┐ 2 + 1 рд╣реЗ 3 рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдирд╛рд╣реА. Demo рдиреЗ рдкреНрд░рддреА 1 рдЖрдгрд┐ 2 рд╡рд╛рдЪрд▓реНрдпрд╛ рдЖрдгрд┐ рддреЗ рдирд╢рд┐рдмрд╛рдиреЗ рдмрд░реЛрдмрд░ рдЖрд▓реЗ. рддреБрдордЪреНрдпрд╛ snippet рдиреЗ рдкреНрд░рддреА 2 рдЖрдгрд┐ 3 рд╡рд╛рдЪрд▓реНрдпрд╛ рдЖрдгрд┐ "Monday" рдорд┐рд│рд╛рд▓реЗ. W = 2 рдЕрд╕рддрд╛рдирд╛ рджреЛрди рдкреНрд░рддреАрдВрдЪреА рдкреНрд░рддреНрдпреЗрдХ рдЬреЛрдбреА "Tuesday" рд╢реЛрдзрддреЗ. Quorum рдореНрд╣рдгрдЬреЗ "рдЬрд╛рд╕реНрдд рдкреНрд░рддреА рд╡рд╛рдЪрд╛" рдирд╡реНрд╣реЗ. рддреЗ рдореНрд╣рдгрдЬреЗ "read рдЧрдЯ рдЖрдгрд┐ write рдЧрдЯ рдПрдХрдореЗрдХрд╛рдВрдирд╛ рдЫреЗрджрддреАрд▓ рдЕрд╕реЗ рдХрд░рд╛".

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ DynamoDB reads default рдиреЗ eventually consistent рдЕрд╕рддрд╛рдд. Strongly consistent read рдорд╛рдЧрд╛ (рддреНрдпрд╛рд▓рд╛ рджреБрдкреНрдкрдЯ read capacity рд▓рд╛рдЧрддреЗ, рдЖрдгрд┐ global tables рдЪреЗ рд╕реНрд╡рддрдГрдЪреЗ рдирд┐рдпрдо рдЕрд╕рддрд╛рдд):

aws dynamodb get-item --table-name notices \
    --key '{"class_id":{"S":"3A"},"created_at":{"S":"2026-09-27T09:00:00Z"}}' \
    --consistent-read

Cassandra: рдкреНрд░рддреНрдпреЗрдХ query рд╕рд╛рдареА level рдард░рд╡рд╛. Read рдЖрдгрд┐ write рджреЛрдиреНрд╣реАрд╕рд╛рдареА QUORUM рджрд┐рд▓реНрдпрд╛рд╕ R + W > N рдорд┐рд│рддреЗ:

CONSISTENCY QUORUM;
SELECT * FROM notices WHERE class_id = '3A' LIMIT 20;

Synchronous replicas рд╕рд╣ PostgreSQL: commit рддреЛрдкрд░реНрдпрдВрдд рдерд╛рдВрдмрддреЛ рдЬреЛрдкрд░реНрдпрдВрдд рджреЛрдирдкреИрдХреА рдПрдХрд╛ standby рдХрдбреЗ рддреЛ рдкреЛрд╣реЛрдЪрдд рдирд╛рд╣реА (primary рд╡рд░реАрд▓ postgresql.conf):

synchronous_standby_names = 'ANY 1 (standby_a, standby_b)'
synchronous_commit = on

App рдордзреНрдпреЗ read-your-writes: рдкрд╛рд▓рдХ рдХрд┐рдВрд╡рд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдиреЗ рд▓рд┐рд╣рд┐рд▓реНрдпрд╛рд╡рд░, рдХрд╛рд╣реА seconds рддрд┐рдЪреЗ reads primary рдХрдбреЗ рдкрд╛рдард╡рд╛:

def db_for_read(session):
    if time.time() - session.get("last_write_at", 0) < 5:   # 5 s > the usual replica lag
        return PRIMARY
    return REPLICA

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ design рдЪреНрдпрд╛ data table рдордзреНрдпреЗ рдПрдХ "consistency" column рдареЗрд╡рд╛: strong, read-your-writes рдХрд┐рдВрд╡рд╛ eventual тАФ рдЖрдгрд┐ рдХрд╛ рдпрд╛рдЪреА рдПрдХ рдУрд│. рдордЧ replica lag рд╡рд░ alarm рд▓рд╛рд╡рд╛ (рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде RDS read replicas рд╕рд╛рдареА CloudWatch рдордзреАрд▓ ReplicaLag), рдХрд╛рд░рдг "eventual" рддреЛрдкрд░реНрдпрдВрддрдЪ рдЪрд╛рд▓рддреЗ рдЬреЛрдкрд░реНрдпрдВрдд "eventual" рдХрдореА рд╡реЗрд│рд╛рдЪреЗ рдЖрд╣реЗ.

тПня╕П рдкреБрдвреЗ

рдкреНрд░рддреА рдЕрд╕рд▓реЗрд▓реА рдПрдХ service рдЖрддрд╛ рд╕реНрдкрд╖реНрдЯ рдЭрд╛рд▓реА. рдкрдг рд╢рд╛рд│реЗрдЪреНрдпрд╛ рд╕рд╣рд▓реАрдЪреЗ booking рддреАрди services рдирд╛ рд╕реНрдкрд░реНрд╢ рдХрд░рддреЗ: seats, payment рдЖрдгрд┐ bus. рддрд┐рд╕рд░реА рдирд╛рд╣реА рдореНрд╣рдгрд╛рд▓реА рддрд░? Services рдУрд▓рд╛рдВрдбреВрди transactions.

git checkout lesson-11-distributed-transactions

ЁЯФБ Lesson 10 тАФ Consistency: three copies of the notice book

ЁЯУН You are here: Lesson 10 of 18 ┬╖ Next: lesson-11-distributed-transactions


ЁЯУж What's in this branch

Lessons 01тАУ09, plus the question every copy raises: when the data lives in more than one place, which copy tells the truth? You learn strong and eventual consistency, replicas and replication lag, quorums (R + W > N), read-your-writes, monotonic reads, CAP and PACELC in plain words, and how to choose per kind of data.

ЁЯзТ Explain like I'm 5

Dipika's planning office keeps the notice book in three staff rooms, so that one locked room does not stop everyone.

Katrina changes a notice: "The exam moves to Tuesday." She writes it in the book of room 1. A runner then carries the change to rooms 2 and 3. The runner takes a little time.

Aishwarya is in room 3. She reads the book before the runner arrives. She sees "Monday". Nothing is broken. Her copy is only late.

Dipika makes a rule: "Write the change in two books before you say done. Read two books and keep the page with the higher page number." Two books written plus two books read is four тАФ more than three rooms. So the two groups always share a room, and the reader always finds the new page.

One day the corridor is blocked. Room 3 cannot reach the others. A parent asks room 3. Room 3 has two choices: answer with what it has (maybe old), or say "please wait" (stay correct). It cannot do both. That choice is CAP.

ЁЯЧ║я╕П Diagram

flowchart LR
    w["тЬНя╕П Katrina writes v8<br/>exam moved to Tuesday"]
    subgraph n3["ЁЯУЪ N = 3 copies"]
      c1["room 1 ┬╖ v8"]
      c2["room 2 ┬╖ v8"]
      c3["room 3 ┬╖ v7 ┬╖ lag"]
    end
    w -->|"W = 2: wait for 2 copies"| c1
    w -->|"W = 2"| c2
    w -.->|"later"| c3
    r["ЁЯСА Aishwarya reads<br/>R = 2, keep the newest"]
    r --> c2
    r --> c3
    ok["2 + 2 > 3 тЖТ the groups overlap<br/>she sees v8"]
    r --> ok

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/system-design/lesson-diagrams.html#l10

тЭУ What

ЁЯдФ Why

Because lessons 05тАУ09 made copies everywhere: caches, read replicas, events that update other services. Every copy can be late. The planning office must decide, per kind of data, how late is acceptable. Wrong in one direction, a parent pays a fee twice. Wrong in the other direction, the whole app waits for a view counter.

The notice board, decided per data type:

data choice why
fee payments strong money must never be counted twice or lost
grades strong a parent must not see an old grade after a correction
who belongs to which class (access) strong a removed parent must lose access at once
a notice, for its author read-your-writes Katrina must see her own post after "posted"
a notice, for other parents eventual (seconds) a few seconds late is fine
"read by 312 parents" counters, likes eventual nobody is harmed by a count that is 2 seconds old

ЁЯФз How (in this repo)

quorum_ok(n, r, w) in design/blocks.py returns r + w > n. quorum_read(copies, r) takes a list of (version, value) copies, reads the first r of them and keeps the highest version. consistency() in design/demo.py tries four R/W pairs for N = 3, then reads three copies where only one has the new version 8.

ЁЯзк Try it

python3 design/demo.py consistency
python3 - <<'EOF'
import sys; sys.path.insert(0, "design"); from blocks import quorum_ok, quorum_read
for n in (3, 5):
    strong = [f"R={r} W={w}" for r in range(1, n + 1) for w in range(1, n + 1) if quorum_ok(n, r, w)]
    print(f"N={n}: {len(strong)} of {n * n} choices are strong ┬╖ smallest pair with R = W: R=W={n // 2 + 1}")
copies = [(8, "exam moved to Tuesday"), (7, "exam on Monday"), (7, "exam on Monday")]
print("W=1 (only copy 1 has v8):", "R=1 from copy 3 тЖТ", quorum_read(copies[2:], 1)[1], "┬╖ R=2 from copies 2,3 тЖТ", quorum_read(copies[1:], 2)[1])
copies = [(8, "exam moved to Tuesday"), (8, "exam moved to Tuesday"), (7, "exam on Monday")]
print("W=2 (copies 1,2 have v8):", "R=2 from copies 2,3 тЖТ", quorum_read(copies[1:], 2)[1], "┬╖ R=2 from copies 1,3 тЖТ", quorum_read(copies[::2], 2)[1])
EOF
python3 design/test_design.py

тЬЕ Verify тАФ what you should see

consistency prints:

тФАтФА 3 copies (N=3): which read/write sizes are sure to see the latest write? (R + W > N)
   R=1 W=1 тЖТ eventual: may read a stale copy
   R=1 W=3 тЖТ strong: overlaps
   R=2 W=2 тЖТ strong: overlaps
   R=3 W=1 тЖТ strong: overlaps
   copies [(7, 'exam on Monday'), (8, 'exam moved to Tuesday'), (7, 'exam on Monday')] ┬╖ read R=1 тЖТ 'exam on Monday' ┬╖ read R=2 тЖТ 'exam moved to Tuesday'
   CAP: during a network split you choose тАФ answer (maybe stale) or refuse (stay correct)
   most apps: strong for money and grades, eventual for likes and view counts

Your snippet prints:

N=3: 6 of 9 choices are strong ┬╖ smallest pair with R = W: R=W=2
N=5: 15 of 25 choices are strong ┬╖ smallest pair with R = W: R=W=3
W=1 (only copy 1 has v8): R=1 from copy 3 тЖТ exam on Monday ┬╖ R=2 from copies 2,3 тЖТ exam on Monday
W=2 (copies 1,2 have v8): R=2 from copies 2,3 тЖТ exam moved to Tuesday ┬╖ R=2 from copies 1,3 тЖТ exam moved to Tuesday

The tests include тЬЕ L10 R + W > N overlaps; R=1 W=1 does not and end with 18/18 passed.

ЁЯПБ What you just proved

With 3 copies, 6 of the 9 R/W pairs are strong; with 5 copies the smallest equal pair is R = W = 3 тАФ a majority. Look closely at the demo's R = 2 read: it found "Tuesday", but only one copy had version 8 (W = 1), and 2 + 1 is not more than 3. The demo read copies 1 and 2 and was lucky. Your snippet read copies 2 and 3 and got "Monday". With W = 2, every pair of two copies finds "Tuesday". A quorum is not "read more copies". It is "make the read group and the write group overlap".

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ DynamoDB reads are eventually consistent by default. Ask for a strongly consistent read (it costs twice the read capacity, and global tables add their own rules):

aws dynamodb get-item --table-name notices \
    --key '{"class_id":{"S":"3A"},"created_at":{"S":"2026-09-27T09:00:00Z"}}' \
    --consistent-read

Cassandra: set the level per query. QUORUM for both read and write gives R + W > N:

CONSISTENCY QUORUM;
SELECT * FROM notices WHERE class_id = '3A' LIMIT 20;

PostgreSQL with synchronous replicas: a commit waits until one of two standbys has it (postgresql.conf on the primary):

synchronous_standby_names = 'ANY 1 (standby_a, standby_b)'
synchronous_commit = on

Read-your-writes in the app: after a parent or a teacher writes, send her reads to the primary for a few seconds:

def db_for_read(session):
    if time.time() - session.get("last_write_at", 0) < 5:   # 5 s > the usual replica lag
        return PRIMARY
    return REPLICA

ЁЯПн Why this matters in production: put a "consistency" column in the data table of every design: strong, read-your-writes or eventual тАФ and one line of why. Then alarm on replica lag (for example ReplicaLag in CloudWatch for RDS read replicas), because "eventual" is only acceptable while "eventual" is short.

тПня╕П Next

One service with copies is now clear. But a school trip booking touches three services: seats, payment and the bus. What if the third one says no? Transactions across services.

git checkout lesson-11-distributed-transactions
тЖР PreviousservicesNext тЖТdistributed transactions

This page is the lesson's README from the lesson-10-consistency branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.