ЁЯПл The SchoolтА║ЁЯЧДя╕П DatabasesтА║ЁЯУИ рдзрдбрд╛ 10 тАФ Scaling: рдЕрдзрд┐рдХ рдХрд╛рд░рдХреВрди, рдЕрдзрд┐рдХ рдЦреЛрд▓реНрдпрд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУИ рдзрдбрд╛ 10 тАФ Scaling: рдЕрдзрд┐рдХ рдХрд╛рд░рдХреВрди, рдЕрдзрд┐рдХ рдЦреЛрд▓реНрдпрд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдорд╛рдЧреАрд▓: lesson-09-backups-recovery ┬╖ рдкреБрдвреАрд▓: lesson-11-nosql-other-rooms


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

рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ рдЦреЛрд▓реА рдЧрд░реНрджреАрдиреЗ рднрд░рд▓реА рдХреА рдХрд╛рдп рдХрд░рд╛рдпрдЪреЗ: connection pools, caches, read replicas (рдЖрдгрд┐ рддреНрдпрд╛рдВрдЪрд╛ lag), partitions, sharding тАФ рдЖрдгрд┐ teams рдкреНрд░рддреНрдпрдХреНрд╖рд╛рдд рдХреЛрдгрддреНрдпрд╛ рдХреНрд░рдорд╛рдиреЗ рд╣реЗ рд╡рд╛рдкрд░рддрд╛рдд.

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

рд╢рд╛рд│рд╛ рдореЛрдареА рдЭрд╛рд▓реА. рд░реЗрдХреЙрд░реНрдб рд░реВрдордЪреНрдпрд╛ рджрд╛рд░рд╛рдкрд░реНрдпрдВрдд рд░рд╛рдВрдЧ рд▓рд╛рдЧрд▓реА рдЖрд╣реЗ. рдЖрддрд╛ рдХрд╛рдп? рджрд░ рд╡реЗрд│реА рддреАрдЪ рдпрд╛рджреА, рддреНрдпрд╛рдЪ рдХреНрд░рдорд╛рдиреЗ:

  1. рдЖрдзреА рдкреНрд░рд╢реНрди рд╕реБрдзрд╛рд░рд╛ тАФ рдмрд╣реБрддреЗрдХ рд░рд╛рдВрдЧрд╛ рдореНрд╣рдгрдЬреЗ рдПрдХрдЪ рд╣рд│реВ рдкреНрд░рд╢реНрди рд╣рдЬрд╛рд░ рд╡реЗрд│рд╛ рд╡рд┐рдЪрд╛рд░рд▓реЗрд▓рд╛ (рд╣рд░рд╡рд▓реЗрд▓рд╛ card catalogue, рдзрдбрд╛ 06; рдПрдХ N+1, рдзрдбрд╛ 12).
  2. рдХрд╛рд░рдХреБрдирд╛рдВрдЪрд╛ pool ЁЯЪ░ тАФ рдкреНрд░рддреНрдпреЗрдХ request рд╕рд╛рдареА рдирд╡реЗ connection рдЙрдШрдбрдгреЗ рдореНрд╣рдгрдЬреЗ рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рд╣реБрдгреНрдпрд╛рд╕рд╛рдареА рдирд╡рд╛ рдХрд╛рд░рдХреВрди рдиреЗрдордгреЗ. рдПрдХ pool рдареЗрд╡рд╛; рддреЗрдЪ рдкреБрдиреНрд╣рд╛ рд╡рд╛рдкрд░рд╛.
  3. рдЧрд░рдо рдЙрддреНрддрд░рд╛рдВрдЪрд╛ рд╕реВрдЪрдирд╛ рдлрд▓рдХ ЁЯзК (рдПрдХ cache, рдЙрджрд╛. Redis) тАФ "3A рдордзреНрдпреЗ рдХрд┐рддреА рд╡рд┐рджреНрдпрд╛рд░реНрдереА?" рд╣рд╛ рдкреНрд░рд╢реНрди рджрд░ рд╕реЗрдХрдВрджрд╛рд▓рд╛ рд╡рд┐рдЪрд╛рд░рд▓рд╛ рдЬрд╛рддреЛ рдЖрдгрд┐ рджрд┐рд╡рд╕рд╛рддреВрди рджреЛрдирджрд╛рдЪ рдмрджрд▓рддреЛ. рдЙрддреНрддрд░ counter рд╡рд░ рдПрдХрд╛ рдорд┐рдирд┐рдЯрд╛рд╕рд╛рдареА рд▓рд╛рд╡реВрди рдареЗрд╡рд╛.
  4. рдореЛрдареА рдЦреЛрд▓реА тАФ рдЬрд╛рд╕реНрдд memory рдЖрдгрд┐ рдЬрд▓рдж disks рдЦреВрдк рдлрд░рдХ рдкрд╛рдбрддрд╛рдд.
  5. рдЦреЛрд▓реАрдЪреНрдпрд╛ рдлрдХреНрдд-рд╡рд╛рдЪрдгреНрдпрд╛рдЪреНрдпрд╛ рдкреНрд░рддреА ЁЯУЦ (replicas) тАФ рд╡рд╛рдЪрдгрд╛рд░реЗ рдкреНрд░рддреАрдВрдХрдбреЗ рдЬрд╛рддрд╛рдд, рд▓рд┐рд╣рд┐рдгрд╛рд░реЗ рдПрдХрд╛рдЪ рдореВрд│ рдЦреЛрд▓реАрдХрдбреЗ. рдкреНрд░рддреА рдереЛрдбреНрдпрд╛ рдорд╛рдЧреЗ рдЕрд╕рддрд╛рдд (replication lag): рд╕реНрд╡рддрдГ рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ рдореВрд│ рдЦреЛрд▓реАрддреВрдирдЪ рд╡рд╛рдЪрд╛.
  6. Partitions тАФ рд╣рдЬреЗрд░реАрдЪреА рдиреЛрдВрджрд╡рд╣реА рд╡рд░реНрд╖рд╛рдиреБрд╕рд╛рд░ рд╡рд┐рднрд╛рдЧрд▓реЗрд▓реА; рдЬреБрдиреА рд╡рд░реНрд╖реЗ рд╣рд│реВ рдХрдкрд╛рдЯрд╛рд╡рд░.
  7. Shards ЁЯПв тАФ рдкреНрд░рджреЗрд╢ рдХрд┐рдВрд╡рд╛ рд╢рд╛рд│реЗрдиреБрд╕рд╛рд░ рд╡реЗрдЧрд│реНрдпрд╛ рдЦреЛрд▓реНрдпрд╛. рд╢реЗрд╡рдЯрдЪрд╛ рдЙрдкрд╛рдп: рдЦреЛрд▓реНрдпрд╛рдВрдкрд▓реАрдХрдбрдЪреЗ рдкреНрд░рд╢реНрди рдЖрдгрд┐ рдЦреЛрд▓реНрдпрд╛рдВрдкрд▓реАрдХрдбрдЪреЗ transactions рдХрдареАрдг рд╣реЛрддрд╛рдд.

рдмрд╣реБрддреЗрдХ рд╢рд╛рд│рд╛рдВрдирд╛ рдкрд╛рдпрд▒реНрдпрд╛ 6тАУ7 рдХрдзреАрдЪ рд▓рд╛рдЧрдд рдирд╛рд╣реАрдд. рдЬреНрдпрд╛рдВрдирд╛ рд▓рд╛рдЧрд▓реНрдпрд╛, рддреНрдпрд╛ рдЖрдзреА 1тАУ5 рдХрд░реВрдирдЪ рддрд┐рдереЗ рдкреЛрд╣реЛрдЪрд▓реНрдпрд╛.

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

flowchart LR
    q["ЁЯРМ the queue"] --> f["1 fix queries + indexes"] --> p["2 connection pool"] --> c["3 cache hot answers"] --> b["4 bigger machine"] --> r["5 read replicas (lag!)"] --> pt["6 partitions"] --> s["7 shards тАФ last"]
    w["тЬНя╕П writes: one primary; Multi-AZ standby is for surviving a fire, not for speed"]
    r -.-> w

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг scaling рдЪреНрдпрд╛ рдЧреЛрд╖реНрдЯреА рджреЛрдирдкреИрдХреА рдПрдХрд╛ рдкреНрд░рдХрд╛рд░реЗ рдлрд╕рддрд╛рдд: рдПрдЦрд╛рджреНрдпрд╛ blog рдиреЗ рд╕рд╛рдВрдЧрд┐рддрд▓реЗ рдореНрд╣рдгреВрди 50 GB database shard рдХрд░рдгреЗ (рдПрдХ рд╡рд░реНрд╖ рд╡рд╛рдпрд╛), рдХрд┐рдВрд╡рд╛ рдХрдзреАрдЪ index рди рдЬреЛрдбрддрд╛ database рд▓рд╛ "рд╣рд│реВ" рдореНрд╣рдгрдгреЗ (рдПрдХ customer рдЧрдорд╛рд╡рд▓рд╛). рд╣реА рд╢рд┐рдбреА рджреЛрдиреНрд╣реАрдкрд╛рд╕реВрди рдмрдЪрд╛рд╡ рдЖрд╣реЗ тАФ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рдпрд░реА рдкреБрдврдЪреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рд╕реНрд╡рд╕реНрдд рдЖрд╣реЗ.

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

SQLite рд╣реА рдПрдХрдЪ file рдЖрд╣реЗ, рдореНрд╣рдгреВрди рдЗрдереЗ replicas рдЖрдгрд┐ shards рдХрд╛рдЧрджрд╛рд╡рд░рдЪреЗ рд╕рд░рд╛рд╡ рдЖрд╣реЗрдд тАФ рдкрдг pool, cache рдЖрдгрд┐ "рдЖрдзреА query рд╕реБрдзрд╛рд░рд╛" рд╣реА рдкрд╛рдпрд░реА рдЦрд░реА рдЖрд╣реЗ: рдзрдбрд╛ 06 рдЪрд╛ index рдЖрдгрд┐ рдзрдбрд╛ 12 рдЪрд╛ N+1 рдореНрд╣рдгрдЬреЗ рдкрд╛рдпрд░реА 1; рдЦрд╛рд▓рдЪрд╛ code рдореНрд╣рдгрдЬреЗ рдкрд╛рдпрд▒реНрдпрд╛ 2тАУ3 рд▓рд╣рд╛рди рд░реВрдкрд╛рдд.

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

python3 - <<'EOF'
import sqlite3, time
# rung 2: a pool in miniature тАФ one connection reused vs opened per request
t=time.perf_counter()
for _ in range(2000): sqlite3.connect("db/school.db").execute("SELECT 1").fetchone()
per_request=(time.perf_counter()-t)*1000
c=sqlite3.connect("db/school.db"); t=time.perf_counter()
for _ in range(2000): c.execute("SELECT 1").fetchone()
pooled=(time.perf_counter()-t)*1000
print(f"2000 queries тАФ new connection each: {per_request:.0f} ms ┬╖ one reused connection: {pooled:.0f} ms")
# rung 3: a cache with a TTL for a hot, slow-ish question
cache={}; TTL=60
def class_size(cls):
    hit=cache.get(cls)
    if hit and time.time()-hit[1] < TTL: return hit[0], "cache"
    n=c.execute("SELECT COUNT(*) FROM students s JOIN classes k ON k.id=s.class_id WHERE k.name=?", (cls,)).fetchone()[0]
    cache[cls]=(n, time.time()); return n, "db"
print(class_size("3A"), class_size("3A"))
EOF
# paper exercise (5 min): your app shows "your enrolment was saved" then lists students from a replica тАФ what can the user see, and what do you change?

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

рдкреНрд░рддреНрдпреЗрдХ query рд╕рд╛рдареА рдирд╡реЗ connection рдЙрдШрдбрдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рдкреБрдиреНрд╣рд╛ рд╡рд╛рдкрд░рд▓реЗрд▓реЗ connection рдХрд┐рддреНрдпреЗрдХ рдкрдЯ рдЬрд▓рдж рдЖрд╣реЗ (pool рдЪреА рдкрд╛рдпрд░реА, рдореЛрдЬреВрди рдкрд╛рд╣рд┐рд▓реЗрд▓реА); рджреБрд╕рд░рд╛ class_size call cache рдордзреВрди рдЙрддреНрддрд░ рджреЗрддреЛ. рддреБрдордЪреЗ рдХрд╛рдЧрджрд╛рд╡рд░рдЪреЗ рдЙрддреНрддрд░: рдирд╡рд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдереА рдХреНрд╖рдгрднрд░ рджрд┐рд╕рдгрд╛рд░ рдирд╛рд╣реА (lag) тАФ рд╕реНрд╡рддрдГ рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ рд╡рд╛рдЪрдгреЗ primary рдХрдбреЗ рдЬрд╛рддреЗ, рдХрд┐рдВрд╡рд╛ UI рд╕рд╛рдард╡рд▓реЗрд▓реА row рд╕реНрдерд╛рдирд┐рдХрдкрдгреЗ рджрд╛рдЦрд╡рддреЗ.

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

рджреЛрди рд╕рд░реНрд╡рд╛рдд рд╕реНрд╡рд╕реНрдд рдкрд╛рдпрд▒реНрдпрд╛ тАФ connections рдкреБрдиреНрд╣рд╛ рд╡рд╛рдкрд░рд╛, рдЧрд░рдо рдЙрддреНрддрд░реЗ cache рдХрд░рд╛ тАФ рдЦрд▒реНрдпрд╛ database рд╡рд░ рдореЛрдЬрд▓реНрдпрд╛, рдЖрдгрд┐ replica-lag рдЪрд╛ рд╕рд╛рдкрд│рд╛ рддреЛ рднреЗрдЯрдгреНрдпрд╛рдЖрдзреАрдЪ рдУрд│рдЦрд▓рд╛.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: "DB рд╣рд│реВ рдЖрд╣реЗ, shard рдХрд░рд╛рдпрдЪреЗ рдХрд╛?" рдЕрд╕реЗ рд╡рд┐рдЪрд╛рд░рд▓реНрдпрд╛рд╡рд░ senior engineer рд╣реАрдЪ рд╢рд┐рдбреА рд╕рд╛рдВрдЧрддреЛ: рдЖрдзреА slow query log рджрд╛рдЦрд╡рд╛. рдЦрд▒реНрдпрд╛ companies рдордзрд▓реЗ рдмрд╣реБрддреЗрдХ scaling рдЪреЗ рд╡рд┐рдЬрдп рдкрд╛рдпрд▒реНрдпрд╛ 1тАУ3 рдордзреВрдирдЪ рдЖрд▓реЗ.

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

рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрди рдиреЛрдВрджрд╡рд╣реАрдд рдмрд╕рдд рдирд╛рд╣реА. NoSQL рдЖрдгрд┐ рдЗрддрд░ рдЦреЛрд▓реНрдпрд╛ тАФ document, key-value, columnar, graph, vector тАФ рдЖрдгрд┐ рдирд┐рд╡рдб рдХрд╢реА рдХрд░рд╛рдпрдЪреА.

git checkout lesson-11-nosql-other-rooms

ЁЯУИ Lesson 10 тАФ Scaling: more clerks, more rooms

ЁЯУН You are here: Lesson 10 of 18 ┬╖ Previous: lesson-09-backups-recovery ┬╖ Next: lesson-11-nosql-other-rooms


ЁЯУж What's in this branch

Lessons 01тАУ09, plus what to do when the room is busy: connection pools, caches, read replicas (and their lag), partitions, sharding тАФ and the order in which teams actually reach for them.

ЁЯзТ Explain like I'm 5

The school grew. The record room has a queue out of the door. What now? The same list, in the same order, every time:

  1. Fix the questions first тАФ most queues are one slow question asked a thousand times (a missing card catalogue, lesson 06; an N+1, lesson 12).
  2. A pool of clerks ЁЯЪ░ тАФ opening a new connection per request is like hiring a clerk per visitor. Keep a pool; reuse them.
  3. A notice board of hot answers ЁЯзК (a cache, e.g. Redis) тАФ "how many students in 3A?" is asked every second and changes twice a day. Pin the answer at the counter for a minute.
  4. A bigger room тАФ more memory and faster disks go a long way.
  5. Read-only copies of the room ЁЯУЦ (replicas) тАФ readers go to the copies, writers to the one original. The copies are a beat behind (replication lag): read your own write from the original.
  6. Partitions тАФ the attendance register split by year; old years on a slower shelf.
  7. Shards ЁЯПв тАФ separate rooms by region or school. Last resort: questions across rooms, and transactions across rooms, get hard.

Most schools never need steps 6тАУ7. The ones that do got there by doing 1тАУ5 first.

ЁЯЧ║я╕П Diagram

flowchart LR
    q["ЁЯРМ the queue"] --> f["1 fix queries + indexes"] --> p["2 connection pool"] --> c["3 cache hot answers"] --> b["4 bigger machine"] --> r["5 read replicas (lag!)"] --> pt["6 partitions"] --> s["7 shards тАФ last"]
    w["тЬНя╕П writes: one primary; Multi-AZ standby is for surviving a fire, not for speed"]
    r -.-> w

тЭУ What

ЁЯдФ Why

Because scaling stories fail in one of two ways: sharding a 50 GB database because a blog said so (a year lost), or never adding an index and calling the database "slow" (a customer lost). The ladder is the defence against both тАФ and every rung is cheaper than the next.

ЁЯФз How (in this repo)

SQLite is one file, so replicas and shards are paper exercises here тАФ but the pool, the cache and the "fix the query first" rung are real: lesson 06's index and lesson 12's N+1 are rungs 1; the code below is rungs 2тАУ3 in miniature.

ЁЯзк Try it

python3 - <<'EOF'
import sqlite3, time
# rung 2: a pool in miniature тАФ one connection reused vs opened per request
t=time.perf_counter()
for _ in range(2000): sqlite3.connect("db/school.db").execute("SELECT 1").fetchone()
per_request=(time.perf_counter()-t)*1000
c=sqlite3.connect("db/school.db"); t=time.perf_counter()
for _ in range(2000): c.execute("SELECT 1").fetchone()
pooled=(time.perf_counter()-t)*1000
print(f"2000 queries тАФ new connection each: {per_request:.0f} ms ┬╖ one reused connection: {pooled:.0f} ms")
# rung 3: a cache with a TTL for a hot, slow-ish question
cache={}; TTL=60
def class_size(cls):
    hit=cache.get(cls)
    if hit and time.time()-hit[1] < TTL: return hit[0], "cache"
    n=c.execute("SELECT COUNT(*) FROM students s JOIN classes k ON k.id=s.class_id WHERE k.name=?", (cls,)).fetchone()[0]
    cache[cls]=(n, time.time()); return n, "db"
print(class_size("3A"), class_size("3A"))
EOF
# paper exercise (5 min): your app shows "your enrolment was saved" then lists students from a replica тАФ what can the user see, and what do you change?

тЬЕ Verify тАФ what you should see

The reused connection is many times faster than a connection per query (the pool rung, measured); the second class_size call answers from cache. Your paper answer: the new student may be missing for a moment (lag) тАФ read-your-own-write goes to the primary, or the UI shows the saved row locally.

ЁЯПБ What you just proved

Two of the cheapest rungs тАФ reuse connections, cache hot answers тАФ measured on a real database, and the replica-lag trap named before you meet it.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: the ladder is what a senior engineer says when asked "the DB is slow, should we shard?": show me the slow query log first. Most scaling wins in real companies came from rungs 1тАУ3.

тПня╕П Next

Not every question fits a register. NoSQL and other rooms тАФ document, key-value, columnar, graph, vector тАФ and how to choose.

git checkout lesson-11-nosql-other-rooms
тЖР Previousbackups recoveryNext тЖТnosql other rooms

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