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

ЁЯй║ рдзрдбрд╛ 12 тАФ Performance рдЖрдгрд┐ operations: рд░реЗрдХреЙрд░реНрдб рд░реВрдо on call

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


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

рд╕рдВрдкреВрд░реНрдг рдХреЛрд░реНрд╕, рдЖрдгрд┐ рд╢реЗрд╡рдЯрдЪрд╛ рдЯрдкреНрдкрд╛: N+1 рдЪрд╛ рд╕рд╛рдкрд│рд╛, slow-query log, рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ metrics, рдЖрдгрд┐ рдЗрддрд░ рд▓реЛрдХ рдЬреНрдпрд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЖрд╣реЗрдд рдЕрд╢рд╛ рд░реЗрдХреЙрд░реНрдб рд░реВрдорд╕рд╛рдареА on-call рдЪреЗрдХрд▓рд┐рд╕реНрдЯ.

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

рдПрдХрд╛ report рд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдереА рдЖрдгрд┐ рддреНрдпрд╛рдЪреНрдпрд╛ рд╢реНрд░реЗрдгреА рд╣рд╡реНрдпрд╛ рдЖрд╣реЗрдд. рдЖрд│рд╢реА рдкрджреНрдзрддреАрдд рдЕрднрд┐рд▓реЗрдЦрдкрд╛рд▓рд╛рд▓рд╛ рдпрд╛рджреАрд╕рд╛рдареА рдПрдХ рдкреНрд░рд╢реНрди, рдордЧ рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рд╕рд╛рдареА рдПрдХ рдкреНрд░рд╢реНрди рдкрд╛рдард╡рд▓рд╛ рдЬрд╛рддреЛ тАФ рдкрд╛рдЪ рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рдВрд╕рд╛рдареА рд╕рд╣рд╛ рдлреЗрд▒реНрдпрд╛, рд╣рдЬрд╛рд░рд╛рд╕рд╛рдареА рдПрдХ рд╣рдЬрд╛рд░ рдПрдХ рдлреЗрд▒реНрдпрд╛. рд╣реЗрдЪ N+1, рдЖрдгрд┐ рдЬрдЧрд╛рддрд▓реЗ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рд╣рд│реВ page рд╣реЗрдЪ рдЖрд╣реЗ. рдЙрдкрд╛рдп рдореНрд╣рдгрдЬреЗ рдПрдХрдЪ рдлреЗрд░реА: рдПрдХ JOIN (рдХрд┐рдВрд╡рд╛ рдПрдХ WHERE id IN (тАж)).

рдЕрд╢рд╛ рдЧреЛрд╖реНрдЯреА users рдЪреНрдпрд╛ рдЖрдзреА рдХрд╢рд╛ рд╢реЛрдзрд╛рдпрдЪреНрдпрд╛? рдЕрднрд┐рд▓реЗрдЦрдкрд╛рд▓ рдПрдХ рд╣рд│реВ-рдкреНрд░рд╢реНрдирд╛рдВрдЪреА рдиреЛрдВрджрд╡рд╣реА ЁЯй║ рдареЗрд╡рддреЗ: N milliseconds рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рд╡реЗрд│ рдШреЗрддрд▓реЗрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрди, рддреНрдпрд╛рдЪреНрдпрд╛ рдорд╛рд░реНрдЧрд╛рд╕рд╣ (EXPLAIN). рддреА рджрд░ рдЖрдард╡рдбреНрдпрд╛рд▓рд╛ рд╡рд╛рдЪрд╛; рд╡рд░рдЪреЗ рджрд╣рд╛ рдореНрд╣рдгрдЬреЗ рддреБрдордЪреЗ рдкреБрдврдЪреЗ рджрд╣рд╛ рд╕реБрдзрд╛рд░.

рдЖрдгрд┐ рдЦреЛрд▓реА on call рдЕрд╕рддрд╛рдирд╛, "рддреА рдирд┐рд░реЛрдЧреА рдЖрд╣реЗ рдХрд╛?" рдпрд╛рдЪреЗ рдЙрддреНрддрд░ рдПрдХ рдЫреЛрдЯреА рдЪреЗрдХрд▓рд┐рд╕реНрдЯ рдХреЛрдгрддреНрдпрд╛рд╣реА dashboard рдлреЗрд░реАрдкреЗрдХреНрд╖рд╛ рд▓рд╡рдХрд░ рджреЗрддреЗ:

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

flowchart LR
    n1["ЁЯРв N+1<br/>SELECT students; then SELECT grades WHERE student_id = ? ├Ч N"]
    one["ЁЯРЗ one JOIN<br/>SELECT s.name, COUNT(g.id) тАж LEFT JOIN тАж GROUP BY s.id"]
    log["ЁЯй║ slow-query log<br/>every query > N ms, with its plan"]
    dash["ЁЯУК QPS ┬╖ p95 latency ┬╖ error rate ┬╖ cache hit rate ┬╖ connections ┬╖ lag ┬╖ disk"]
    n1 -->|"1 rewrite"| one
    log -->|"2 top ten weekly"| one
    dash -->|"3 alert on"| log

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг database install рдХрд░реВрди рд╕рдВрдкрдд рдирд╛рд╣реА, рддреЛ рдЪрд╛рд▓рд╡рд╛рд╡рд╛ рд▓рд╛рдЧрддреЛ. рдЬреА team рджрд░ рдЖрдард╡рдбреНрдпрд╛рд▓рд╛ slow log рд╡рд╛рдЪрддреЗ рдЖрдгрд┐ рджрд░ рддрд┐рдорд╛рд╣реАрд▓рд╛ restore drill рдХрд░рддреЗ, рддрд┐рдЪреЗ on-call рдХрдВрдЯрд╛рд│рд╡рд╛рдгреЗ рдЕрд╕рддреЗ; рдЬреА team рджреЛрдиреНрд╣реАрдкреИрдХреА рдХрд╛рд╣реАрдЪ рдХрд░рдд рдирд╛рд╣реА, рддреА рджреЛрдиреНрд╣реА outage рдордзреВрди рд╢рд┐рдХрддреЗ. рдЪреЗрдХрд▓рд┐рд╕реНрдЯ рд╣рд╛рдЪ рд╕рдЧрд│рд╛ рдлрд░рдХ рдЖрд╣реЗ.

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

db/demo.py рдордзрд▓реЗ nplus1() loop рдЖрдгрд┐ JOIN рджреЛрдиреНрд╣реА рдЪрд╛рд▓рд╡рддреЗ рдЖрдгрд┐ рджреЛрдиреНрд╣реАрдЪреА рд╡реЗрд│ рдореЛрдЬрддреЗ. school.db рд╕рд╛рдареА рдЪреЗрдХрд▓рд┐рд╕реНрдЯ рддреБрдореНрд╣реА рд▓рд┐рд╣рд╛рдпрдЪреА рдЖрд╣реЗ тАФ рдЫреЛрдЯреНрдпрд╛ рдЦреЛрд▓реНрдпрд╛рдВрдирд╛рд╣реА рддреЗрдЪ рдкреНрд░рд╢реНрди рд▓рд╛рдЧрддрд╛рдд, рдлрдХреНрдд рд▓рд╣рд╛рди рдкреНрд░рдорд╛рдгрд╛рдд.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛ тАФ capstone

python3 db/demo.py nplus1
python3 - <<'EOF'
import sqlite3, time
c = sqlite3.connect("db/school.db")
# 1) a slow-query log in 6 lines
def q(sql, *a, threshold_ms=0.05):
    t=time.perf_counter(); rows=c.execute(sql, a).fetchall(); ms=(time.perf_counter()-t)*1000
    if ms > threshold_ms: print(f"ЁЯРв {ms:.3f} ms ┬╖ {sql[:60]} ┬╖ plan: {c.execute('EXPLAIN QUERY PLAN '+sql, a).fetchall()[0][3]}")
    return rows
q("SELECT COUNT(*) FROM attendance WHERE day = '2026-03-03'")          # a candidate for the log
q("SELECT COUNT(*) FROM attendance WHERE student_id = 4242")           # served by lesson 06's index
# 2) the checklist, measured:
print("rows:", c.execute("SELECT COUNT(*) FROM attendance").fetchone()[0], "┬╖ page_size:", c.execute("PRAGMA page_size").fetchone()[0], "┬╖ journal:", c.execute("PRAGMA journal_mode").fetchone()[0])
EOF
# 3) capstone: add a teachers table (L05), a migration for it (L08), an index a new question needs (L06),
#    a query that would be N+1 written as one JOIN, and a one-page on-call checklist for school.db.

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

nplus1 рдПрдХрд╛рдЪ рдЙрддреНрддрд░рд╛рд╕рд╛рдареА 6 queries рд╡рд┐рд░реБрджреНрдз 1 рджрд╛рдЦрд╡рддреЗ; рддреБрдордЪрд╛ slow-query log day query SCAN plan рд╕рд╣ рдЫрд╛рдкрддреЛ (index рд╕рд╛рдареА рдЙрдореЗрджрд╡рд╛рд░) рдЖрдгрд┐ index рдЕрд╕рд▓реЗрд▓реНрдпрд╛ student_id query рд╕рд╛рдареА рдЧрдкреНрдк рд░рд╛рд╣рддреЛ. рдЪреЗрдХрд▓рд┐рд╕реНрдЯ рдЦрд▒реНрдпрд╛ file рд╕рд╛рдареА рдЦрд░реЗ рдЖрдХрдбреЗ рдЫрд╛рдкрддреЗ.

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

рддреБрдореНрд╣реА рд╣рд│реВ рдкреНрд░рд╢реНрди рд╢реЛрдзреВ рд╢рдХрддрд╛, рддреНрдпрд╛рдЪрд╛ рдорд╛рд░реНрдЧ рд╡рд╛рдЪреВ рд╢рдХрддрд╛, рдПрдХрд╛ JOIN рдиреЗ рдХрд┐рдВрд╡рд╛ рдПрдХрд╛ index рдиреЗ рддреЛ рд╕реБрдзрд╛рд░реВ рд╢рдХрддрд╛, рдЖрдгрд┐ рдЦреЛрд▓реА рдирд┐рд░реЛрдЧреА рдЖрд╣реЗ рдХрд╛ рд╣реЗ тАФ рдЖрдХрдбреНрдпрд╛рдВрд╕рд╣ тАФ рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: database on call рдЬрд╛рдгреНрдпрд╛рдЖрдзреА reviewers рд╣реАрдЪ рдЪреЗрдХрд▓рд┐рд╕реНрдЯ рд╡рд╛рдкрд░рддрд╛рдд: slow log рдЪрд╛рд▓реВ, p95 рдЖрдгрд┐ lag рдЕрд╕рд▓реЗрд▓реЗ dashboards, connection limits рдЖрдгрд┐ pooling, drill рдЪреНрдпрд╛ рддрд╛рд░рдЦреЗрд╕рд╣ backups, CI рдордзреНрдпреЗ migrations. рдПрдХ рдЪреБрдХрд▓реЗ рддрд░реА рддреЗрдЪ incident рдмрдирддреЗ.

ЁЯОУ рд░реЗрдХреЙрд░реНрдб рд░реВрдо рдЖрддрд╛ рддреБрдордЪреА тАФ рднрд╛рдЧ 3 рдЕрдзрд┐рдХ рдЦреЛрд▓рд╛рдд рдЬрд╛рддреЛ

рдЦреЛрд▓реА рдЪрд╛рд▓реВ рдЖрд╣реЗ. рднрд╛рдЧ 3 (рдзрдбреЗ 13тАУ18) interviews рдЖрдгрд┐ incidents рдордзреНрдпреЗ рд╡рд┐рдЪрд╛рд░рд▓реЗ рдЬрд╛рдгрд╛рд░реЗ рдкреНрд░рд╢реНрди рдЙрдШрдбрддреЛ: git checkout lesson-13-security.

рддреБрдореНрд╣реА рдХреБрдареВрди рдХреБрдареЗ рдЖрд▓рд╛рдд

рдЪрд┐рдХрдЯ рдЪрд┐рдареНрдареНрдпрд╛ тЖТ рд░реЗрдХреЙрд░реНрдб рд░реВрдо тЖТ рдиреЛрдВрджрд╡рд╣реНрдпрд╛ рдЖрдгрд┐ keys тЖТ рдЕрднрд┐рд▓реЗрдЦрдкрд╛рд▓рд╛рд▓рд╛ рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рдгреЗ тЖТ рдкреЗрдиреНрд╕рд┐рд▓рдордзрд▓реА рдиреЛрдВрджрд╡рд╣реА тЖТ рдПрдХ рддрдереНрдп, рдПрдХ рдЬрд╛рдЧрд╛ тЖТ рдХрд╛рд░реНрдб рдХреЕрдЯрд▓реЙрдЧ тЖТ рджреЛрди рдХрд╛рд░рдХреВрди, рдПрдХ рдиреЛрдВрджрд╡рд╣реА тЖТ рдЪрд╛рд▓реВ рдЕрд╕рддрд╛рдирд╛рдЪ рдиреВрддрдиреАрдХрд░рдг тЖТ рдЖрдЧреАрдкрд╛рд╕реВрди рд╕реБрд░рдХреНрд╖рд┐рдд рдкреНрд░рдд тЖТ рдЕрдзрд┐рдХ рдХрд╛рд░рдХреВрди, рдЕрдзрд┐рдХ рдЦреЛрд▓реНрдпрд╛ тЖТ рдЗрддрд░ рдЦреЛрд▓реНрдпрд╛ тЖТ on call. рддреБрдореНрд╣реА рдлрдХреНрдд databases рд╢рд┐рдХрд▓реНрдпрд╛ рдирд╛рд╣реАрдд тАФ рддреБрдореНрд╣реА рдПрдХ рдЪрд╛рд▓рд╡рддрд╛. ЁЯЧДя╕ПЁЯОУ

рд╢рд╛рд│реЗрддрд▓реЗ рдкреБрдврдЪреЗ рджрд░рд╡рд╛рдЬреЗ: API рд╢рд╛рд│рд╛ рдпрд╛ рдЦреЛрд▓реАрд╕рдореЛрд░ рдПрдХ counter рдареЗрд╡рддреЗ; VectorDB рд╢рд╛рд│рд╛ рд╢реЗрдЬрд╛рд░рдЪреЗ рдЕрд░реНрдерд╛рдЪреЗ рд╕рднрд╛рдЧреГрд╣ рдЙрдШрдбрддреЗ.

git checkout main
python3 db/demo.py     # one last run, for fun

ЁЯй║ Lesson 12 тАФ Performance & operations: the record room on call

ЁЯУН You are here: Lesson 12 of 18 ┬╖ Previous: lesson-11-nosql-other-rooms ┬╖ Next: lesson-13-security


ЁЯУж What's in this branch

The complete course, plus the last mile: the N+1 trap, the slow-query log, the metrics that matter, and the on-call checklist for a record room that other people depend on.

ЁЯзТ Explain like I'm 5

A report needs every student and their grades. The lazy way sends the archivist one question for the list, then one question per student тАФ six trips for five students, a thousand and one trips for a thousand. That is N+1, and it is the most common slow page in the world. The fix is one trip: a JOIN (or one WHERE id IN (тАж)).

How do you find such things before users do? The archivist keeps a slow-question log ЁЯй║: every question that took longer than N milliseconds, with its route (EXPLAIN). Read it weekly; the top ten are your next ten fixes.

And when the room is on call, a short checklist answers "is it healthy?" faster than any dashboard tour:

ЁЯЧ║я╕П Diagram

flowchart LR
    n1["ЁЯРв N+1<br/>SELECT students; then SELECT grades WHERE student_id = ? ├Ч N"]
    one["ЁЯРЗ one JOIN<br/>SELECT s.name, COUNT(g.id) тАж LEFT JOIN тАж GROUP BY s.id"]
    log["ЁЯй║ slow-query log<br/>every query > N ms, with its plan"]
    dash["ЁЯУК QPS ┬╖ p95 latency ┬╖ error rate ┬╖ cache hit rate ┬╖ connections ┬╖ lag ┬╖ disk"]
    n1 -->|"1 rewrite"| one
    log -->|"2 top ten weekly"| one
    dash -->|"3 alert on"| log

тЭУ What

ЁЯдФ Why

Because a database is operated, not installed. The team that reads the slow log weekly and drills a restore quarterly has boring on-call; the team that does neither learns both from an outage. The checklist is the whole difference.

ЁЯФз How (in this repo)

nplus1() in db/demo.py runs the loop and the JOIN and times both. The checklist is yours to write for school.db тАФ small rooms need the same questions, on a smaller scale.

ЁЯзк Try it тАФ the capstone

python3 db/demo.py nplus1
python3 - <<'EOF'
import sqlite3, time
c = sqlite3.connect("db/school.db")
# 1) a slow-query log in 6 lines
def q(sql, *a, threshold_ms=0.05):
    t=time.perf_counter(); rows=c.execute(sql, a).fetchall(); ms=(time.perf_counter()-t)*1000
    if ms > threshold_ms: print(f"ЁЯРв {ms:.3f} ms ┬╖ {sql[:60]} ┬╖ plan: {c.execute('EXPLAIN QUERY PLAN '+sql, a).fetchall()[0][3]}")
    return rows
q("SELECT COUNT(*) FROM attendance WHERE day = '2026-03-03'")          # a candidate for the log
q("SELECT COUNT(*) FROM attendance WHERE student_id = 4242")           # served by lesson 06's index
# 2) the checklist, measured:
print("rows:", c.execute("SELECT COUNT(*) FROM attendance").fetchone()[0], "┬╖ page_size:", c.execute("PRAGMA page_size").fetchone()[0], "┬╖ journal:", c.execute("PRAGMA journal_mode").fetchone()[0])
EOF
# 3) capstone: add a teachers table (L05), a migration for it (L08), an index a new question needs (L06),
#    a query that would be N+1 written as one JOIN, and a one-page on-call checklist for school.db.

тЬЕ Verify тАФ what you should see

nplus1 shows 6 queries vs 1 with the same answer; your slow-query log prints the day query with a SCAN plan (a candidate for an index) and stays silent for the indexed student_id query. The checklist prints real numbers for a real file.

ЁЯПБ What you just proved

You can find the slow question, read its route, fix it with one JOIN or one index, and say тАФ with numbers тАФ whether the room is healthy.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: this is the checklist reviewers use before a database goes on call: slow log on, dashboards with p95 and lag, connection limits and pooling, backups with a drill date, migrations in CI. Miss one and it becomes the incident.

ЁЯОУ The record room is yours тАФ Part 3 goes deeper

The room runs. Part 3 (lessons 13тАУ18) opens the questions interviews and incidents ask: git checkout lesson-13-security.

Where you have been

Sticky notes тЖТ the record room тЖТ registers and keys тЖТ asking the archivist тЖТ the ledger in pencil тЖТ one fact, one place тЖТ the card catalogue тЖТ two clerks, one register тЖТ renovating while open тЖТ the fireproof copy тЖТ more clerks, more rooms тЖТ other rooms тЖТ on call. You didn't just learn databases тАФ you run one. ЁЯЧДя╕ПЁЯОУ

Next doors in the school: the API school puts a counter in front of this room; the VectorDB school opens the meaning hall next door.

git checkout main
python3 db/demo.py     # one last run, for fun
тЖР Previousnosql other roomsNext тЖТsecurity

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