ЁЯПл The SchoolтА║ЁЯЧДя╕П DatabasesтА║ЁЯзСтАНЁЯТ╗ рдзрдбрд╛ 17 тАФ Code рдордзреВрди database
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯзСтАНЁЯТ╗ рдзрдбрд╛ 17 тАФ Code рдордзреВрди database

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 17 ┬╖ рдорд╛рдЧреАрд▓: lesson-16-replication-failover ┬╖ рдкреБрдвреАрд▓: lesson-18-oltp-olap

рднрд╛рдЧ 3 тАФ рдЕрдзрд┐рдХ рдЦреЛрд▓рд╛рдд. рдзрдбреЗ 01тАУ12 рдиреА рд░реЗрдХреЙрд░реНрдб рд░реВрдо рдмрд╛рдВрдзрд▓реА рдЖрдгрд┐ рдЪрд╛рд▓рд╡рд▓реА. рдзрдбреЗ 13тАУ18 рд╣реЗ interviews рдЖрдгрд┐ incidents рдордзреНрдпреЗ рд╡рд┐рдЪрд╛рд░рд▓реЗ рдЬрд╛рдгрд╛рд░реЗ рд╡рд┐рд╖рдп рдЖрд╣реЗрдд тАФ рдкреНрд░рддреНрдпреЗрдХ рддреНрдпрд╛рдЪ school.db рд╡рд░ рдкреНрд░рддреНрдпрдХреНрд╖ рдЪрд╛рд▓рддреЛ.


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

рдзрдбреЗ 01тАУ17, рдЖрдгрд┐ db/demo.py рдордзрд▓реЗ appcode(): 2,000 inserts рдПрдХреЗрдХ рдХрд░реВрди commit рд╡рд┐рд░реБрджреНрдз рдПрдХрд╛рдЪ transaction рдордзреНрдпреЗ, рдЖрдгрд┐ database is locked рд▓рд╛ рднреЗрдЯрдгрд╛рд░рд╛ рдЖрдгрд┐ exponential backoff рд╕рд╣ рддрд┐рд╕рд▒реНрдпрд╛ рдкреНрд░рдпрддреНрдирд╛рдд рдпрд╢рд╕реНрд╡реА рд╣реЛрдгрд╛рд░рд╛ рдПрдХ write.

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

рдПрдХ рдорджрддрдиреАрд╕ рд╡рд░реНрдЧрд╛рддреВрди рд░реЗрдХреЙрд░реНрдб рд░реВрдордХрдбреЗ forms рдШреЗрдКрди рдЬрд╛рддреЗ. рддреА рдЦреЛрд▓реАрдкрд░реНрдпрдВрдд рдЪрд╛рд▓рдд рдЧреЗрд▓реА, рдПрдХ form рджрд┐рд▓рд╛, рд╢рд┐рдХреНрдХреНрдпрд╛рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рд┐рд▓реА, рдЖрдгрд┐ рдкрд░рдд рдЖрд▓реА тАФ 2,000 рд╡реЗрд│рд╛ тАФ рддрд░ рджрд┐рд╡рд╕ рд╕рдВрдкрддреЛ. рдЬрд░ рддреА 2,000 forms рдЪрд╛ рдПрдХ рдЦреЛрдХрд╛ рдШреЗрдКрди рдЧреЗрд▓реА рдЖрдгрд┐ рдПрдХ рд╢рд┐рдХреНрдХрд╛ рдШреЗрддрд▓рд╛, рддрд░ рдХреНрд╖рдгрднрд░ рд▓рд╛рдЧрддреЛ. рдкреНрд░рддреНрдпреЗрдХ COMMIT рдореНрд╣рдгрдЬреЗ рдЦреЛрд▓реАрдкрд░реНрдпрдВрддрдЪреА рдПрдХ рдлреЗрд░реА.

рдХрдзреА рдХрдзреА рджреБрд╕рд░рд╛ рдХрд╛рд░рдХреВрди рдЖрдд рдЕрд╕рд▓реНрдпрд╛рдореБрд│реЗ рдЦреЛрд▓реАрдЪреЗ рджрд╛рд░ рдХреНрд╖рдгрднрд░ рдмрдВрдж рдЕрд╕рддреЗ. рд╢рд╣рд╛рдгреА рдорджрддрдиреАрд╕ рдереЛрдбреА рдерд╛рдВрдмрддреЗ, рдкреБрдиреНрд╣рд╛ рдкреНрд░рдпрддреНрди рдХрд░рддреЗ, рдЖрдгрдЦреА рдереЛрдбреА рдерд╛рдВрдмрддреЗ, рдкреБрдиреНрд╣рд╛ рдкреНрд░рдпрддреНрди рдХрд░рддреЗ тАФ рдЖрдгрд┐ рдХрд╛рдпрдо рджрд╛рд░ рдареЛрдард╛рд╡рдд рд░рд╛рд╣рдгреНрдпрд╛рдРрд╡рдЬреА рдХрд╛рд╣реА рдкреНрд░рдпрддреНрдирд╛рдВрдирдВрддрд░ рдирдореНрд░рдкрдгреЗ рдерд╛рдВрдмрддреЗ.

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

flowchart LR
    req["ЁЯМР request"] --> pool["ЁЯЪ░ pool: borrow a connection"]
    pool --> tx["with conn:  BEGIN тАж COMMIT<br/>parameters (?) only"]
    tx --> ok["тЬЕ return the connection"]
    tx -. "database is locked" .-> retry["тП│ retry: 50 тЖТ 100 тЖТ 200 ms, then give up"]
    retry --> tx

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

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

ЁЯдФ рдХрд╛

рдмрд╣реБрддреЗрдХ "database рд╣рд│реВ рдЖрд╣реЗ" tickets рдореНрд╣рдгрдЬреЗ рдЦрд░реЗрддрд░ "app database рд╢реА рд╡рд╛рдИрдЯ рдкрджреНрдзрддреАрдиреЗ рдмреЛрд▓рддреЗ": рдЦреВрдк commits, рдЦреВрдк connections, рдЦреВрдк round trips, рдЖрдгрд┐ lock рд╕рд╛рдареА рдХреЛрдгрддреАрдЪ рдпреЛрдЬрдирд╛ рдирд╛рд╣реА.

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

db/demo.py рдордзрд▓реЗ appcode() тАФ рддреЗ рджреБрд╕рд▒реНрдпрд╛ connection рдордзреВрди write lock рдзрд░реВрди рдареЗрд╡рддреЗ, рддреНрдпрд╛рдореБрд│реЗ retry loop рд▓рд╛ рдЦрд░рд╛ database is locked рднреЗрдЯрддреЛ, рдордЧ рддреЗ lock рд╕реЛрдбрддреЗ.

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

python3 db/demo.py appcode
python3 - <<'EOF'
import sqlite3, time
c = sqlite3.connect(":memory:"); c.execute("CREATE TABLE t (x)")
for n in (100, 1000, 5000):
    t = time.perf_counter()
    with c: c.executemany("INSERT INTO t VALUES (?)", ((i,) for i in range(n)))
    print(n, "rows in one transaction:", round((time.perf_counter() - t) * 1000, 2), "ms")
EOF

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

рдХрд╛рд╣реАрд╕реЗ рдЕрд╕реЗ commit per row 513 ms ┬╖ one transaction 1 ms (рддреБрдордЪреЗ рдЖрдХрдбреЗ рд╡реЗрдЧрд│реЗ рдЕрд╕рддреАрд▓; рдЧреБрдгреЛрддреНрддрд░ рд╣рд╛рдЪ рдзрдбрд╛ рдЖрд╣реЗ), рдордЧ attempt 1 тАж locked, attempt 2 тАж locked, attempt 3: done.

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

рдПрдХрджрд╛рдЪ commit рдХрд░реВрди рддреБрдореНрд╣реА writes рд╢реЗрдХрдбреЛ рдкрдЯ рдЬрд▓рдж рдХрд░реВ рд╢рдХрддрд╛, рдЖрдгрд┐ рдереЛрдбрд╛ рд╡реЗрд│ рдХреБрд▓реВрдкрдмрдВрдж рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдЦреЛрд▓реАрдд app рдЕрдбрдХреВрди рди рдкрдбрддрд╛ рдХрд┐рдВрд╡рд╛ рддрд┐рдЪреНрдпрд╛рд╡рд░ рдШрд╛рд╡ рди рдШрд╛рд▓рддрд╛ рдЯрд┐рдХрд╡реВ рд╢рдХрддрд╛.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: pool sizes, transaction scope, retry policy рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ request рдорд╛рдЧрдЪреЗ query counts рд╣реЗрдЪ reviewers рдкреНрд░рддреНрдпреЗрдХ service рдЪреНрдпрд╛ data layer рдордзреНрдпреЗ рддрдкрд╛рд╕рддрд╛рдд.

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

git checkout lesson-18-oltp-olap тАФ рдореЛрдареЗ reports рдХреБрдареЗ рдЪрд╛рд▓рд╡рд╛рдпрдЪреЗ.

ЁЯзСтАНЁЯТ╗ Lesson 17 тАФ The database from code

ЁЯУН You are here: Lesson 17 of 18 ┬╖ Previous: lesson-16-replication-failover ┬╖ Next: lesson-18-oltp-olap

Part 3 тАФ going deeper. Lessons 01тАУ12 built and ran the record room. Lessons 13тАУ18 are the topics interviews and incidents ask about тАФ each one runs for real on the same school.db.


ЁЯУж What's in this branch

Lessons 01тАУ17, plus appcode() in db/demo.py: 2,000 inserts committed one by one vs in one transaction, and a write that meets database is locked and succeeds on the third attempt with exponential backoff.

ЁЯзТ Explain like I'm 5

A helper carries forms from the classroom to the record room. If she walks to the room, hands over one form, waits for the stamp, and walks back тАФ 2,000 times тАФ the day is gone. If she carries one box of 2,000 forms and gets one stamp, it takes a moment. Every COMMIT is a walk to the room.

Sometimes the room's door is locked for a moment because another clerk is inside. A sensible helper waits a little, tries again, waits a bit longer, tries again тАФ and gives up politely after a few tries instead of banging on the door forever.

ЁЯЧ║я╕П Diagram

flowchart LR
    req["ЁЯМР request"] --> pool["ЁЯЪ░ pool: borrow a connection"]
    pool --> tx["with conn:  BEGIN тАж COMMIT<br/>parameters (?) only"]
    tx --> ok["тЬЕ return the connection"]
    tx -. "database is locked" .-> retry["тП│ retry: 50 тЖТ 100 тЖТ 200 ms, then give up"]
    retry --> tx

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

тЭУ What

ЁЯдФ Why

Most "the database is slow" tickets are really "the app talks to the database badly": too many commits, too many connections, too many round trips, and no plan for a lock.

ЁЯФз How (in this repo)

appcode() in db/demo.py тАФ it holds the write lock from a second connection, so the retry loop meets a real database is locked, then releases it.

ЁЯзк Try it

python3 db/demo.py appcode
python3 - <<'EOF'
import sqlite3, time
c = sqlite3.connect(":memory:"); c.execute("CREATE TABLE t (x)")
for n in (100, 1000, 5000):
    t = time.perf_counter()
    with c: c.executemany("INSERT INTO t VALUES (?)", ((i,) for i in range(n)))
    print(n, "rows in one transaction:", round((time.perf_counter() - t) * 1000, 2), "ms")
EOF

тЬЕ Verify тАФ what you should see

Something like commit per row 513 ms ┬╖ one transaction 1 ms (your numbers differ; the ratio is the lesson), then attempt 1 тАж locked, attempt 2 тАж locked, attempt 3: done.

ЁЯПБ What you just proved

You can make writes hundreds of times faster by committing once, and make an app survive a briefly locked room without hanging or hammering it.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: pool sizes, transaction scope, retry policy and query counts per request are what reviewers check in the data layer of every service.

тПня╕П Next

git checkout lesson-18-oltp-olap тАФ where the big reports should run.

тЖР Previousreplication failoverNext тЖТoltp olap

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