ЁЯПл The SchoolтА║ЁЯПОя╕П PerformanceтА║ЁЯЪ░ рдзрдбрд╛ 09 тАФ Queueing, Little рдЖрдгрд┐ Amdahl: рдкрд╛рдгреНрдпрд╛рдЪреНрдпрд╛ рдЯреЗрдмрд▓рд╛рд╡рд░рдЪреА рд░рд╛рдВрдЧ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЪ░ рдзрдбрд╛ 09 тАФ Queueing, Little рдЖрдгрд┐ Amdahl: рдкрд╛рдгреНрдпрд╛рдЪреНрдпрд╛ рдЯреЗрдмрд▓рд╛рд╡рд░рдЪреА рд░рд╛рдВрдЧ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧреЗ: lesson-08-concurrency ┬╖ рдкреБрдвреЗ: lesson-10-database-queries


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

рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ busy service рд╕рдордЬрд╛рд╡рдгрд╛рд░реЗ рддреАрди рдирд┐рдпрдо: queueing (server рдЬрд╕рд╛ рдЕрдзрд┐рдХ busy рд╣реЛрддреЛ, рддрд╕реЗ рдерд╛рдВрдмрдгреЗ рдЖрдзреА рд╣рд│реВ рд╡рд╛рдврддреЗ, рдордЧ рд╕реНрдлреЛрдЯ рд╣реЛрддреЛ), Little's law (рдПрдХрд╛ рд╡реЗрд│реА system рдордзреНрдпреЗ рдХрд┐рддреА requests рдЖрд╣реЗрдд) рдЖрдгрд┐ Amdahl's law (рдЬреЛ рднрд╛рдЧ рд╡рд╛рдЯреВрди рджреЗрддрд╛ рдпреЗрдд рдирд╛рд╣реА рддреЛ speed-up рд▓рд╛ рдорд░реНрдпрд╛рджрд╛ рдШрд╛рд▓рддреЛ). perf/demo.py рдордзреАрд▓ queueing() рдЖрдгрд┐ perf/sim.py рдордзреАрд▓ mm1_wait_ms, simulate_queue, littles_law рдЖрдгрд┐ amdahl.

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

рдкрд╛рдгреНрдпрд╛рдЪреЗ рдЯреЗрдмрд▓ рдПрдХрдЪ рдЖрд╣реЗ ЁЯЪ░, рдЖрдгрд┐ рдРрд╢реНрд╡рд░реНрдпрд╛рд▓рд╛ рдПрдХ рдХрдк рднрд░рд╛рдпрд▓рд╛ 10 рд╕реЗрдХрдВрдж рд▓рд╛рдЧрддрд╛рдд.

рдЗрддрдХреЗ рд╡рд╛рдИрдЯ, рдЗрддрдХреНрдпрд╛ рд▓рд╡рдХрд░ рдХрд╛ рд╣реЛрддреЗ? рдХрд╛рд░рдг рдзрд╛рд╡рдкрдЯреВ рд╕рдорд╛рди рдЕрдВрддрд░рд╛рдиреЗ рдпреЗрдд рдирд╛рд╣реАрдд. рдХрдзреА рдХрдзреА рддрд┐рдШреА рдПрдХрджрдо рдпреЗрддрд╛рдд. рдЯреЗрдмрд▓ рдЬрд╡рд│рдЬрд╡рд│ рдиреЗрд╣рдореА busy рдЕрд╕реЗрд▓ рддрд░ рддреНрдпрд╛ рдЧрд░реНрджреА рднрд░реВрди рдХрд╛рдврдгреНрдпрд╛рд╕рд╛рдареА рддреНрдпрд╛рд▓рд╛ рдХрдзреАрдЪ рд╢рд╛рдВрдд рдХреНрд╖рдг рдорд┐рд│рдд рдирд╛рд╣реА.

рдХреНрд░реАрдбрд╛ рджрд┐рдирд╛рдЪреЗ рдЖрдгрдЦреА рджреЛрди рдирд┐рдпрдо:

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

flowchart LR
    arr["ЁЯПГ runners arrive<br/>at random"] --> q["ЁЯЪ╢ЁЯЪ╢ЁЯЪ╢ the queue"] --> t["ЁЯЪ░ one table<br/>10 ms a cup"]
    u50["50% busy тЖТ 20 ms"] --> u90["90% busy тЖТ 100 ms"] --> u99["99% busy тЖТ 1000 ms"]
    little["Little: L = ╬╗W<br/>80/s ├Ч 50 ms = 4"]
    amd["Amdahl, 90% parallel:<br/>8 runners 4.71├Ч ┬╖ endless 10├Ч"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг "server рдлрдХреНрдд 90% CPU рд╡рд░ рдЖрд╣реЗ, рдЕрдЬреВрди рдЬрд╛рдЧрд╛ рдЖрд╣реЗ" рдЕрд╢рд╛рдиреЗрдЪ outages рд╕реБрд░реВ рд╣реЛрддрд╛рдд. 90% busy рд╡рд░ рд╕рд░рд╛рд╕рд░реА request рдЖрдзреАрдЪ 10 service times рдерд╛рдВрдмрддреЗ, рдЖрдгрд┐ рдЫреЛрдЯрд╛рд╕рд╛ burst рддреА рдЦреВрдк рд╡рд░ рдврдХрд▓рддреЛ. Latency-sensitive service рд╕рд╛рдареА рд╕реБрд░рдХреНрд╖рд┐рдд рдЬрд╛рдЧрд╛ 100% busy рдЪреНрдпрд╛ рдмрд▒реНрдпрд╛рдЪ рдЦрд╛рд▓реА рдЖрд╣реЗ. рдЖрдгрд┐ 16 рд╡рд╛ worker рдЬреЛрдбрд▓реНрдпрд╛рдиреЗ рдлрд╛рд░рд╢реА рдорджрдд рдХрд╛ рдЭрд╛рд▓реА рдирд╛рд╣реА рд╣реЗ Amdahl рд╕рдордЬрд╛рд╡рддреЛ: serial рднрд╛рдЧрдЪ рдЖрдзреАрдкрд╛рд╕реВрди рдмрд╣реБрддреЗрдХ рд╡реЗрд│ рдШреЗрдд рд╣реЛрддрд╛.

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

perf/sim.py рдордзреАрд▓ mm1_wait_ms(service_ms, util) рд╣реЗ M/M/1 рд╕реВрддреНрд░ S / (1 тИТ ╧Б) рдЖрд╣реЗ. simulate_queue(rate, service_ms, n, seed) рдПрдХрд╛ first-come first-served server рдЪреЗ simulation рдХрд░рддреЗ, arrivals рдордзреАрд▓ seeded random (exponential) рдЕрдВрддрд░реЗ рдЖрдгрд┐ random service times рд╕рд╣, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ request рдЪрд╛ system рдордзреАрд▓ рд╡реЗрд│ рдкрд░рдд рджреЗрддреЗ. littles_law(rate, wait_ms) рдореНрд╣рдгрдЬреЗ ╬╗W; amdahl(p, n) рдореНрд╣рдгрдЬреЗ speed-up рдЪреЗ рд╕реВрддреНрд░. perf/demo.py рдордзреАрд▓ queueing() рд╕реВрддреНрд░рд╛рдЪреА 20,000 simulated рдзрд╛рд╡рдкрдЯреВрдВрд╢реА рддреБрд▓рдирд╛ рдХрд░рддреЗ.

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

python3 perf/demo.py queueing
python3 - <<'EOF'
import sys; sys.path.insert(0, "perf"); from sim import mm1_wait_ms, littles_law, amdahl
for s in (10, 5):
    print(f"service {s:>2} ms: " + " ┬╖ ".join(f"{u:.0%} busy тЖТ {mm1_wait_ms(s, u):.0f} ms" for u in (0.5, 0.7, 0.9)))
print("Little: 200 req/s ├Ч 250 ms =", littles_law(200, 250), "requests in flight")
for p in (0.5, 0.9, 0.99):
    print(f"parallel share {p:.0%}: 8 workers тЖТ {amdahl(p, 8):.2f}├Ч ┬╖ the limit {1 / (1 - p):.0f}├Ч")
EOF

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

queueing рд╣реЗ рдЫрд╛рдкрддреЗ:

тФАтФА one water table, 10 ms to serve a runner ┬╖ M/M/1 approximation: time at the table = 10 ms ├╖ (1 тИТ utilisation)
   utilisation 50% тЖТ formula     20 ms ┬╖ simulated (20,000 runners)     20 ms
   utilisation 80% тЖТ formula     50 ms ┬╖ simulated (20,000 runners)     49 ms
   utilisation 90% тЖТ formula    100 ms ┬╖ simulated (20,000 runners)    104 ms
   utilisation 95% тЖТ formula    200 ms ┬╖ simulated (20,000 runners)    186 ms
   utilisation 99% тЖТ formula   1000 ms ┬╖ simulated (20,000 runners)    519 ms
   near 100% busy, the wait explodes (and a short simulation has not caught up with it yet)
тФАтФА Little's law L = ╬╗W: 80 runners/s ├Ч 50 ms = 4 runners at the table on average
тФАтФА Amdahl's law: 90% of the relay can be shared out, 10% is one runner's leg that nobody can help with
   2 runners тЖТ 1.82├Ч ┬╖ 4 runners тЖТ 3.08├Ч ┬╖ 8 runners тЖТ 4.71├Ч ┬╖ 16 runners тЖТ 6.40├Ч ┬╖ endless runners тЖТ 10.00├Ч

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

service 10 ms: 50% busy тЖТ 20 ms ┬╖ 70% busy тЖТ 33 ms ┬╖ 90% busy тЖТ 100 ms
service  5 ms: 50% busy тЖТ 10 ms ┬╖ 70% busy тЖТ 17 ms ┬╖ 90% busy тЖТ 50 ms
Little: 200 req/s ├Ч 250 ms = 50.0 requests in flight
parallel share 50%: 8 workers тЖТ 1.78├Ч ┬╖ the limit 2├Ч
parallel share 90%: 8 workers тЖТ 4.71├Ч ┬╖ the limit 10├Ч
parallel share 99%: 8 workers тЖТ 7.48├Ч ┬╖ the limit 100├Ч

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

50тАУ90% busy рд╡рд░ simulation рд╕реВрддреНрд░рд╛рд╢реА рдЬреБрд│рд▓реЗ (20, 49, 104 ms). 99% рд╡рд░ рддреЗ рдлрдХреНрдд 519 ms рджрд╛рдЦрд╡рд▓реЗ, 1000 рдирд╡реНрд╣реЗ тАФ рдЗрддрдХреА busy рд░рд╛рдВрдЧ рддрд┐рдЪреНрдпрд╛ рд╕рд░рд╛рд╕рд░реАрдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪрд╛рдпрд▓рд╛ рдЦреВрдк рд╡реЗрд│ рдШреЗрддреЗ, рдЖрдгрд┐ рд╣реАрдЪ рдПрдХ рдзреЛрдХреНрдпрд╛рдЪреА рд╕реВрдЪрдирд╛ рдЖрд╣реЗ: 100% рдЬрд╡рд│ рдкреНрд░рддреАрдХреНрд╖рд╛ рдлрдХреНрдд рд▓рд╛рдВрдм рдирд╕рддреЗ, рддреА рдЕрдирд┐рд╢реНрдЪрд┐рдд рдЕрд╕рддреЗ. Service time рдЕрд░реНрдзрд╛ рдХреЗрд▓реНрдпрд╛рдиреЗ (рдзрдбрд╛ 03 рд╕рд╛рд░рдЦрд╛ рдЙрдкрд╛рдп) рддреНрдпрд╛рдЪ utilisation рд╡рд░ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рддреАрдХреНрд╖рд╛ рдЕрд░реНрдзреА рдЭрд╛рд▓реА. рдЖрдгрд┐ 50% serial рднрд╛рдЧ рдЕрд╕рддрд╛рдирд╛ 8 workers рдиреА рдлрдХреНрдд 1.78├Ч рджрд┐рд▓реЗ.

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

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

Database connection pool рдЪрд╛ рдЖрдХрд╛рд░ Little's law рдиреЗ рдард░рд╡рд╛. 200 requests/s рд╡рд░, рдкреНрд░рддреНрдпреЗрдХ request 25 ms connection рдзрд░реВрди рдареЗрд╡рдд рдЕрд╕реЗрд▓ рддрд░: 200 ├Ч 0.025 = рд╕рд░рд╛рд╕рд░реА 5 connections busy тАФ bursts рд╕рд╛рдареА рдЬрд╛рджрд╛ рдЬрд╛рдЧрд╛ рдареЗрд╡рд╛, рдЖрдгрд┐ рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рд╛ рдХреА рдкреНрд░рддреНрдпреЗрдХ app instance рдЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ pool рдЕрд╕рддреЛ. рдЦрд▒реНрдпрд╛ account рд╡рд░, SQLAlchemy:

engine = create_engine(DB_URL, pool_size=10, max_overflow=5, pool_timeout=2)

рдлрдХреНрдд CPU рдирд╡реНрд╣реЗ, рддрд░ utilisation рдЖрдгрд┐ queue length рд╡рд░ рд▓рдХреНрд╖ рдареЗрд╡рд╛. Linux рд╡рд░:

vmstat 1           # r = runnable threads waiting for a CPU; above the core count means a queue
iostat -x 1        # %util and aqu-sz (average queue size) for each disk

Prometheus рдордзреНрдпреЗ, worker pool рдЪрд╛ busy рд╡рд╛рдЯрд╛ (metric рдЪреА рдирд╛рд╡реЗ рддреБрдордЪреНрдпрд╛ exporter рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕рддрд╛рдд):

sum(rate(worker_busy_seconds_total[5m])) / count(worker_up)

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ latency-sensitive service рд▓рд╛ рдПрдХ utilisation target рджреНрдпрд╛ (рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде "60тАУ70% busy рдЪреНрдпрд╛ рд╡рд░ рдЧреЗрд▓реНрдпрд╛рд╕ scale out рдХрд░рд╛"), queue length рд╡рд░ alert рд▓рд╛рд╡рд╛, рдЖрдгрд┐ workers рд╡рд╛рдврд╡рдгреНрдпрд╛рдЖрдзреА serial рднрд╛рдЧ рд╢реЛрдзрд╛.

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

Requests рд╕рд░реНрд╡рд╛рдзрд┐рдХ рд░рд╛рдВрдЧреЗрдд рдерд╛рдВрдмрддрд╛рдд рддреА рдЬрд╛рдЧрд╛ рдореНрд╣рдгрдЬреЗ database. рдкреБрдвреЗ: рдкреНрд░рддреНрдпреЗрдХ рдкрддреНрд░рдХ рд╡рд╛рдЪреВрди, рдХреА index рд╡рд╛рдкрд░реВрди, рдПрдХрд╛ рдзрд╛рд╡рдкрдЯреВрдЪреЗ laps рд╢реЛрдзрдгреЗ тАФ EXPLAIN QUERY PLAN.

git checkout lesson-10-database-queries

ЁЯЪ░ Lesson 09 тАФ Queueing, Little & Amdahl: the queue at the water table

ЁЯУН You are here: Lesson 09 of 12 ┬╖ Previous: lesson-08-concurrency ┬╖ Next: lesson-10-database-queries


ЁЯУж What's in this branch

Lessons 01тАУ08, plus the three laws that explain a busy service: queueing (as a server gets busier, waiting grows slowly, then explodes), Little's law (how many requests are in the system at once) and Amdahl's law (the part you cannot share out caps the speed-up). queueing() in perf/demo.py and mm1_wait_ms, simulate_queue, littles_law and amdahl in perf/sim.py.

ЁЯзТ Explain like I'm 5

There is one water table ЁЯЪ░, and Aishwarya takes 10 seconds to fill a cup.

Why does it get so bad, so fast? Because runners do not arrive evenly. Sometimes three come at once. When the table is almost always busy, it never gets a quiet moment to catch up with those bunches.

Two more rules of sports day:

ЁЯЧ║я╕П Diagram

flowchart LR
    arr["ЁЯПГ runners arrive<br/>at random"] --> q["ЁЯЪ╢ЁЯЪ╢ЁЯЪ╢ the queue"] --> t["ЁЯЪ░ one table<br/>10 ms a cup"]
    u50["50% busy тЖТ 20 ms"] --> u90["90% busy тЖТ 100 ms"] --> u99["99% busy тЖТ 1000 ms"]
    little["Little: L = ╬╗W<br/>80/s ├Ч 50 ms = 4"]
    amd["Amdahl, 90% parallel:<br/>8 runners 4.71├Ч ┬╖ endless 10├Ч"]

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

тЭУ What

ЁЯдФ Why

Because "the server is only at 90% CPU, there is room left" is how outages start. At 90% busy the average request already waits 10 service times, and a small burst pushes it much higher. The safe place for a latency-sensitive service is well below 100% busy. And Amdahl explains why adding a 16th worker barely helped: the serial part was already most of the time.

ЁЯФз How (in this repo)

mm1_wait_ms(service_ms, util) in perf/sim.py is the M/M/1 formula S / (1 тИТ ╧Б). simulate_queue(rate, service_ms, n, seed) simulates one first-come first-served server with seeded random (exponential) gaps between arrivals and random service times, and returns every request's time in the system. littles_law(rate, wait_ms) is ╬╗W; amdahl(p, n) is the speed-up formula. queueing() in perf/demo.py compares the formula with 20,000 simulated runners.

ЁЯзк Try it

python3 perf/demo.py queueing
python3 - <<'EOF'
import sys; sys.path.insert(0, "perf"); from sim import mm1_wait_ms, littles_law, amdahl
for s in (10, 5):
    print(f"service {s:>2} ms: " + " ┬╖ ".join(f"{u:.0%} busy тЖТ {mm1_wait_ms(s, u):.0f} ms" for u in (0.5, 0.7, 0.9)))
print("Little: 200 req/s ├Ч 250 ms =", littles_law(200, 250), "requests in flight")
for p in (0.5, 0.9, 0.99):
    print(f"parallel share {p:.0%}: 8 workers тЖТ {amdahl(p, 8):.2f}├Ч ┬╖ the limit {1 / (1 - p):.0f}├Ч")
EOF

тЬЕ Verify тАФ what you should see

queueing prints:

тФАтФА one water table, 10 ms to serve a runner ┬╖ M/M/1 approximation: time at the table = 10 ms ├╖ (1 тИТ utilisation)
   utilisation 50% тЖТ formula     20 ms ┬╖ simulated (20,000 runners)     20 ms
   utilisation 80% тЖТ formula     50 ms ┬╖ simulated (20,000 runners)     49 ms
   utilisation 90% тЖТ formula    100 ms ┬╖ simulated (20,000 runners)    104 ms
   utilisation 95% тЖТ formula    200 ms ┬╖ simulated (20,000 runners)    186 ms
   utilisation 99% тЖТ formula   1000 ms ┬╖ simulated (20,000 runners)    519 ms
   near 100% busy, the wait explodes (and a short simulation has not caught up with it yet)
тФАтФА Little's law L = ╬╗W: 80 runners/s ├Ч 50 ms = 4 runners at the table on average
тФАтФА Amdahl's law: 90% of the relay can be shared out, 10% is one runner's leg that nobody can help with
   2 runners тЖТ 1.82├Ч ┬╖ 4 runners тЖТ 3.08├Ч ┬╖ 8 runners тЖТ 4.71├Ч ┬╖ 16 runners тЖТ 6.40├Ч ┬╖ endless runners тЖТ 10.00├Ч

Your snippet prints:

service 10 ms: 50% busy тЖТ 20 ms ┬╖ 70% busy тЖТ 33 ms ┬╖ 90% busy тЖТ 100 ms
service  5 ms: 50% busy тЖТ 10 ms ┬╖ 70% busy тЖТ 17 ms ┬╖ 90% busy тЖТ 50 ms
Little: 200 req/s ├Ч 250 ms = 50.0 requests in flight
parallel share 50%: 8 workers тЖТ 1.78├Ч ┬╖ the limit 2├Ч
parallel share 90%: 8 workers тЖТ 4.71├Ч ┬╖ the limit 10├Ч
parallel share 99%: 8 workers тЖТ 7.48├Ч ┬╖ the limit 100├Ч

ЁЯПБ What you just proved

The simulation agreed with the formula at 50тАУ90% busy (20, 49, 104 ms). At 99% it showed only 519 ms, not 1000 тАФ a queue that busy takes a very long time to reach its average, which is itself a warning: near 100%, the wait is not just long, it is unpredictable. Halving the service time (lesson 03's kind of fix) halved every wait at the same utilisation. And with a 50% serial part, 8 workers gave only 1.78├Ч.

тЪая╕П Common mistakes

ЁЯПн In production

Size a database connection pool with Little's law. At 200 requests/s, each holding a connection for 25 ms: 200 ├Ч 0.025 = 5 connections busy on average тАФ give it headroom for bursts, and remember every app instance has its own pool. On a real account, SQLAlchemy:

engine = create_engine(DB_URL, pool_size=10, max_overflow=5, pool_timeout=2)

Watch utilisation and queue length, not just CPU. On Linux:

vmstat 1           # r = runnable threads waiting for a CPU; above the core count means a queue
iostat -x 1        # %util and aqu-sz (average queue size) for each disk

In Prometheus, the busy share of a worker pool (metric names depend on your exporter):

sum(rate(worker_busy_seconds_total[5m])) / count(worker_up)

ЁЯПн Why this matters in production: give each latency-sensitive service a utilisation target (for example "scale out above 60тАУ70% busy"), alert on queue length, and look for the serial part before adding workers.

тПня╕П Next

The most common place requests queue is the database. Next: finding one runner's laps by reading every sheet, or by using the index тАФ EXPLAIN QUERY PLAN.

git checkout lesson-10-database-queries
тЖР PreviousconcurrencyNext тЖТdatabase queries

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