ЁЯФБ рдзрдбрд╛ 07 тАФ I/O, N+1 рдЖрдгрд┐ batching: рдкреНрд░рддреНрдпреЗрдХ рд╣рд╕реНрддрд╛рдВрддрд░рд╛рд▓рд╛ рдПрдХ рдлреЗрд░реА рд▓рд╛рдЧрддреЗ
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 07 ┬╖ рдорд╛рдЧреЗ: lesson-06-caching ┬╖ рдкреБрдвреЗ: lesson-08-concurrency
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ06, рдЕрдзрд┐рдХ I/O: database, disk рдХрд┐рдВрд╡рд╛ рджреБрд╕рд▒реНрдпрд╛ service рдХрдбреЗ рдЬрд╛рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрд▓рд╛
рдард░рд╛рд╡рд┐рдХ toll рднрд░рд╛рд╡рд╛ рд▓рд╛рдЧрддреЛ, рддреБрдореНрд╣реА рдиреЗрдд рдЕрд╕рд▓реЗрд▓реА рдЧреЛрд╖реНрдЯ рдХрд┐рддреАрд╣реА рд▓рд╣рд╛рди рдЕрд╕рд▓реА рддрд░реА. рдкреНрд░рд╕рд┐рджреНрдз рдирд╛рд╕рд╛рдбреА рдореНрд╣рдгрдЬреЗ N+1 query: рдпрд╛рджреАрд╕рд╛рдареА рдПрдХ
query, рдордЧ рдкреНрд░рддреНрдпреЗрдХ item рд╕рд╛рдареА рдЖрдгрдЦреА рдПрдХ. рдЙрдкрд╛рдп рдЖрд╣реЗрдд batching (IN (...), рдПрдХ
JOIN) рдЖрдгрд┐ buffering. perf/demo.py рдордзрд▓реЗ io() рдЖрдгрд┐
perf/sim.py рдордзрд▓реЗ make_db, counting, n_plus_one, in_batch, joined рдЖрдгрд┐ write_lines.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдкреНрд░рдорд╛рдгрдкрддреНрд░рд╛рдВрд╕рд╛рдареА рд░рд┐рд▓реЗ рд╕рдВрдШрд╛рд▓рд╛ рдкреНрд░рддреНрдпреЗрдХрд╛рдЪрд╛ рд╕рд░реНрд╡реЛрддреНрддрдо lap рд╣рд╡рд╛ рдЖрд╣реЗ. ЁЯПЕ Laps рдореИрджрд╛рдирд╛рдЪреНрдпрд╛ рдкрд▓реАрдХрдбрдЪреНрдпрд╛ office рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ рдЖрд╣реЗрдд.
- рд╣рд│реВ рдкрджреНрдзрдд: рджреАрдкрд┐рдХрд╛ office рдХрдбреЗ рдзрд╛рд╡рдд рдЬрд╛рддреЗ. "рдХреГрдкрдпрд╛ рдорд▓рд╛ рдзрд╛рд╡рдкрдЯреВрдВрдЪреА рдпрд╛рджреА рджреНрдпрд╛." рдзрд╛рд╡рдд рдкрд░рдд рдпреЗрддреЗ. рдордЧ 50 рдкреИрдХреА рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВрд╕рд╛рдареА: office рдХрдбреЗ рдзрд╛рд╡рдд рдЬрд╛рддреЗ, "рдпрд╛ рдзрд╛рд╡рдкрдЯреВрдЪрд╛ рд╕рд░реНрд╡реЛрддреНрддрдо lap рдХреЛрдгрддрд╛?", рдзрд╛рд╡рдд рдкрд░рдд рдпреЗрддреЗ. рдореНрд╣рдгрдЬреЗ рдореИрджрд╛рди рдУрд▓рд╛рдВрдбреВрди 51 рдлреЗрд▒реНрдпрд╛. ЁЯПГтАНтЩАя╕ПЁЯПГтАНтЩАя╕ПЁЯПГтАНтЩАя╕П
- рддреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рдЪрд╛рдВрдЧрд▓реА: рдпрд╛рджреАрд╕рд╛рдареА рдПрдХ рдлреЗрд░реА, рдЖрдгрд┐ рдирд╛рд╡рд╛рдВрдЪреА рд╕рдВрдкреВрд░реНрдг рдпрд╛рджреА рдШреЗрдКрди рдПрдХ рдлреЗрд░реА: "рдпрд╛ рд╕рдЧрд│реНрдпрд╛рдВрдЪреЗ рд╕рд░реНрд╡реЛрддреНрддрдо laps рджреНрдпрд╛, рдХреГрдкрдпрд╛." 2 рдлреЗрд▒реНрдпрд╛.
- рд╕рд░реНрд╡реЛрддреНрддрдо: рдПрдХ рдлреЗрд░реА: "рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВ рддреНрдпрд╛рдЪреНрдпрд╛ рд╕рд░реНрд╡реЛрддреНрддрдо lap рд╕рд╣ рджреНрдпрд╛." 1 рдлреЗрд░реА.
рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрд▓рд╛ рдлрдХреНрдд рддрд┐рдереЗ рдЬрд╛рдКрди рдкрд░рдд рдпрд╛рдпрд▓рд╛рдЪ рд╡реЗрд│ рд▓рд╛рдЧрддреЛ, рдЙрддреНрддрд░ рдПрдХ рдЫреЛрдЯрд╛ рдЖрдХрдбрд╛ рдЕрд╕рд▓рд╛ рддрд░реА. рдореНрд╣рдгреВрди рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрдд рдЕрдиреЗрдХ рдЧреЛрд╖реНрдЯреА рдиреНрдпрд╛.
рд▓рд┐рд╣рд┐рдгреНрдпрд╛рдЪреЗрд╣реА рдЕрд╕реЗрдЪ рдЖрд╣реЗ. рдкреНрд░рддреНрдпреЗрдХ result рд╡реЗрдЧрд│реНрдпрд╛ card рд╡рд░ рд▓рд┐рд╣реВрди post рдХрд░рдгреЗ рд╣рд│реВ рдЖрд╣реЗ. рдЖрдзреА рд╕рдВрдкреВрд░реНрдг рдкрд╛рдХреАрдЯ рднрд░реВрди рддреЗ рдПрдХрджрд╛рдЪ post рдХрд░рдгреЗ рдЬрд▓рдж рдЖрд╣реЗ. рдпрд╛рд▓рд╛рдЪ buffering рдореНрд╣рдгрддрд╛рдд. тЬЙя╕П
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
app["ЁЯПГ the app"] -->|"1 query: the runners"| db[("ЁЯЧГя╕П SQLite")]
app -->|"+50 queries: one per runner"| db
n1["N+1 тЖТ 51 queries<br/>51 ms at 1 ms a round trip"]
b["IN (...) тЖТ 2 queries"]
j["JOIN + GROUP BY тЖТ 1 query"]
w["10,000 lines ┬╖ 210,000 bytes:<br/>unbuffered 10,000 writes ┬╖ buffered 26"]
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/performance/lesson-diagrams.html#l07
тЭУ рдХрд╛рдп
- I/O (input/output) тАФ CPU рдЪреНрдпрд╛ рдмрд╛рд╣реЗрд░ рдЬрд╛рдгрд╛рд░реА рдХреЛрдгрддреАрд╣реА рдЧреЛрд╖реНрдЯ: disk, network, database, рджреБрд╕рд░реА service. рд╕рд╣рд╕рд╛ рдЧрдгрдиреЗрдкреЗрдХреНрд╖рд╛ рдЦреВрдк рд╣рд│реВ, рдЖрдгрд┐ program рдмрд╣реБрддреЗрдХ рд╡реЗрд│ рд╡рд╛рдЯ рдкрд╛рд╣рдд рдЕрд╕рддреЛ.
- Round trip тАФ request рдкрд╛рдард╡рд╛, рдЙрддреНрддрд░рд╛рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рд╛. рддреНрдпрд╛рдЪрд╛ рдард░рд╛рд╡рд┐рдХ рднрд╛рдЧ (network latency, parsing, system call) рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрд▓рд╛ рднрд░рд╛рд╡рд╛ рд▓рд╛рдЧрддреЛ, рдЙрддреНрддрд░ рдХрд┐рддреАрд╣реА рд▓рд╣рд╛рди рдЕрд╕рд▓реЗ рддрд░реА. рдПрдХрд╛рдЪ data center рдордзреНрдпреЗ database рдкрд░реНрдпрдВрддрдЪрд╛ round trip рдмрд▒реНрдпрд╛рдЪрджрд╛ millisecond рдЪреНрдпрд╛ рдЕрдВрд╢рд╛рдкрд╛рд╕реВрди рдХрд╛рд╣реА milliseconds рдкрд░реНрдпрдВрдд рдЕрд╕рддреЛ; regions рдУрд▓рд╛рдВрдбреВрди, рджрд╣рд╛рдкрдЯ milliseconds.
- N+1 queries тАФ рдпрд╛рджреАрд╕рд╛рдареА 1 query рдЕрдзрд┐рдХ рддрдкрд╢реАрд▓рд╛рдВрд╕рд╛рдареА N queries. рддреЗ ORM рдорд╛рдЧреЗ рд╕рд╣рдЬ
рд▓рдкрддреЗ:
for runner in Runner.objects.all(): runner.laps...objects рд╡рд░рдЪреНрдпрд╛ loop рд╕рд╛рд░рдЦреЗ рджрд┐рд╕рддреЗ рдЖрдгрд┐ рдкреНрд░рддреНрдпрдХреНрд╖рд╛рдд queries рд╡рд░рдЪрд╛ loop рдЕрд╕рддреЛ. - Batching тАФ рдЕрдиреЗрдХ items рдиреЗрдгрд╛рд░реА рдПрдХ request:
WHERE id IN (?, ?, ...), рдПрдХJOIN, bulk insert (executemany,COPY), batch API call. Batches рдирд╛ рдорд░реНрдпрд╛рджрд╛ рдЕрд╕рддрд╛рдд: SQLite default рдиреБрд╕рд╛рд░ рдПрдХрд╛ statement рдордзреНрдпреЗ рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд 32,766?parameters рдареЗрд╡реВ рджреЗрддреЗ (version 3.32 рдЖрдзреА 999). - Buffering тАФ рдЕрдиреЗрдХ рдЫреЛрдЯреЗ writes memory рдордзреНрдпреЗ рдЧреЛрд│рд╛ рдХрд░рд╛ рдЖрдгрд┐ рдПрдХрд╛ рдореЛрдареНрдпрд╛ write рдореНрд╣рдгреВрди рдкрд╛рдард╡рд╛. File рд╡рд░рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ
raw write рд╣рд╛ рдПрдХ system call рдЕрд╕рддреЛ, рдЬреНрдпрд╛рдЪрд╛ рдЦрд░реНрдЪ memory рдордзреНрдпреЗ bytes copy рдХрд░рдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рдЦреВрдк рдЬрд╛рд╕реНрдд рдЕрд╕рддреЛ.
Python рдЪреЗ
open()default рдиреБрд╕рд╛рд░ buffer рдХрд░рддреЗ;flush()buffer рдкрд╛рдард╡рддреЗ, рдЖрдгрд┐os.fsync()operating system рд▓рд╛ рддреЛ disk рд╡рд░ рдареЗрд╡рд╛рдпрд▓рд╛ рд╕рд╛рдВрдЧрддреЗ. - Connection reuse тАФ TCP (рдЖрдгрд┐ TLS) connection рдЙрдШрдбрдгреЗ рд╣реЗрдЪ рд╕реНрд╡рддрдГ рдЕрдиреЗрдХ round trips рдЕрд╕рддреЗ. Connection pools рдЖрдгрд┐ HTTP keep-alive рддреЛ рдЦрд░реНрдЪ рдкреНрд░рддреНрдпреЗрдХ request рд▓рд╛ рдирд╡реНрд╣реЗ, рдПрдХрджрд╛рдЪ рднрд░рддрд╛рдд.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рд░реЛрдЬрдЪреНрдпрд╛ рдмрд╣реБрддреЗрдХ services рдЖрдкрд▓рд╛ рд╡реЗрд│ I/O рдордзреНрдпреЗрдЪ рдШрд╛рд▓рд╡рддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХреА 1 ms рдЪреНрдпрд╛ 51 queries рдХрд░рдгрд╛рд░рд╛ endpoint 51 ms рд╡рд╛рдЯ рдкрд╛рд╣рдгреНрдпрд╛рдд рдШрд╛рд▓рд╡рддреЛ, Python рдХрд┐рддреАрд╣реА рдЬрд▓рдж рдЕрд╕рд▓рд╛ рддрд░реА, рдЖрдгрд┐ load рдЦрд╛рд▓реА рддреНрдпрд╛ queries database рдордзреНрдпреЗ рд░рд╛рдВрдЧреЗрддрд╣реА рдЙрднреНрдпрд╛ рд░рд╛рд╣рддрд╛рдд (рдзрдбрд╛ 09). N+1 local database рдЖрдгрд┐ 3 rows рдЕрд╕рд▓реЗрд▓реНрдпрд╛ laptop рд╡рд░ рджрд┐рд╕рддрд╣реА рдирд╛рд╣реА. рддреЗ production рдордзреНрдпреЗ рджрд┐рд╕рддреЗ, network рдЖрдгрд┐ 200 rows рд╕рд╣.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
perf/sim.py рдордзрд▓реЗ make_db() 50
рдзрд╛рд╡рдкрдЯреВ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХреА 4 laps рдЕрд╕рд▓реЗрд▓рд╛ in-memory SQLite database рдмрдирд╡рддреЗ. counting(conn) sqlite3 рдЪрд╛ trace callback рд╕реБрд░реВ рдХрд░рддреЗ, рдЬреЛ
connection рдЪрд╛рд▓рд╡рдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ SQL statement рд╕рд╣ рдПрдХ function call рдХрд░рддреЛ тАФ рдореНрд╣рдгреВрди query counts рдЦрд░реЗ рдЖрд╣реЗрдд.
n_plus_one, in_batch рдЖрдгрд┐ joined рддреЗрдЪ рдЙрддреНрддрд░ рддреАрди рдкреНрд░рдХрд╛рд░реЗ рдорд┐рд│рд╡рддрд╛рдд. SQLite рдпрд╛рдЪ
process рдЪреНрдпрд╛ рдЖрдд рдЪрд╛рд▓рддреЛ, рдореНрд╣рдгреВрди рдЗрдереЗ рддреНрдпрд╛рдЪреНрдпрд╛ queries рд╕реНрд╡рд╕реНрдд рдЖрд╣реЗрдд; "рдкреНрд░рддреНрдпреЗрдХ round trip рд▓рд╛ 1 ms" рд╣рд╛ network рд╡рд░рдЪреНрдпрд╛
database server рдЪрд╛ model рдЖрд╣реЗ. write_lines(lines, buffered) CountingRaw рдордзреВрди рд▓рд┐рд╣рд┐рддреЗ, рдЬреА
рдкреНрд░рддреНрдпреЗрдХ raw write рдореЛрдЬрдгрд╛рд░реА рдмрдирд╛рд╡рдЯ file рдЖрд╣реЗ, Python рдЪреНрдпрд╛ рдЦрд▒реНрдпрд╛ io.BufferedWriter рд╕рд╣ рдХрд┐рдВрд╡рд╛ рддреНрдпрд╛рд╢рд┐рд╡рд╛рдп.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 perf/demo.py io
python3 - <<'EOF'
import sys; sys.path.insert(0, "perf"); import sim
for n in (10, 50, 200):
db = sim.make_db(n_runners=n); log = sim.counting(db)
sim.n_plus_one(db); a = len(log); log.clear(); sim.joined(db); b = len(log)
print(f"{n:>3} runners тЖТ N+1 {a:>3} queries ┬╖ JOIN {b} ┬╖ at 2 ms a round trip: {a * 2:>3} ms vs {b * 2} ms")
db = sim.make_db(n_runners=3); log = sim.counting(db); sim.n_plus_one(db)
print("\n".join(log))
lines = [b"x" * 20 + b"\n"] * 10_000
for size in (1024, 8192, 65536):
print(f"buffer {size:>5} bytes тЖТ {sim.write_lines(lines, True, size)[0]:>4} raw writes")
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
io рд╣реЗ рдЫрд╛рдкрддреЗ:
тФАтФА best lap for each of 50 runners, from SQLite (sqlite3's trace callback counts the queries)
N+1: one query for the runners, then one per runner тЖТ 51 queries
batched with IN (...) тЖТ 2 queries
one JOIN + GROUP BY тЖТ 1 query ┬╖ same answer all three ways: True
at 1 ms round trip to a database server: 51 ms vs 2 ms vs 1 ms of waiting (here SQLite is in-process)
тФАтФА writing 10,000 result lines (210,000 bytes) unbuffered тЖТ 10,000 raw writes
тФАтФА writing 10,000 result lines (210,000 bytes) buffered (8 KB) тЖТ 26 raw writes
рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:
10 runners тЖТ N+1 11 queries ┬╖ JOIN 1 ┬╖ at 2 ms a round trip: 22 ms vs 2 ms
50 runners тЖТ N+1 51 queries ┬╖ JOIN 1 ┬╖ at 2 ms a round trip: 102 ms vs 2 ms
200 runners тЖТ N+1 201 queries ┬╖ JOIN 1 ┬╖ at 2 ms a round trip: 402 ms vs 2 ms
SELECT id, name FROM runners ORDER BY id
SELECT MIN(lap_ms) FROM laps WHERE runner_id = 1
SELECT MIN(lap_ms) FROM laps WHERE runner_id = 2
SELECT MIN(lap_ms) FROM laps WHERE runner_id = 3
buffer 1024 bytes тЖТ 209 raw writes
buffer 8192 bytes тЖТ 26 raw writes
buffer 65536 bytes тЖТ 4 raw writes
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
N+1 data рд╕реЛрдмрдд рд╡рд╛рдврддреЗ тАФ 11, 51, 201 queries тАФ рддрд░ JOIN 1 рд╡рд░рдЪ рд░рд╛рд╣рддреЛ. Trace log рдиреЗрдордХреЗ рддреЗрдЪ рджрд╛рдЦрд╡рддреЛ рдЬреЗ ORM рд▓рдкрд╡реЗрд▓: рддреНрдпрд╛рдЪ рдЖрдХрд╛рд░рд╛рдЪреА query, рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВрд╕рд╛рдареА рдПрдХрджрд╛. Buffering рдиреЗ 10,000 raw writes 26 рд╡рд░ рдЖрдгрд▓реЗ (210,000 bytes ├╖ 8192 тЙИ 25.6); рдореЛрдард╛ buffer рдореНрд╣рдгрдЬреЗ рдХрдореА writes, рдЖрдгрд┐ program flush рдХрд░рдгреНрдпрд╛рдЖрдзреА crash рдЭрд╛рд▓реНрдпрд╛рд╕ рдЬрд╛рд╕реНрдд data рд╣рд░рд╡рдгреЗ.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- rows рд╡рд░рдЪреНрдпрд╛ loop рдЪреНрдпрд╛ рдЖрдд query (рдХрд┐рдВрд╡рд╛ HTTP call)
- ORM template рдордзреНрдпреЗ relations lazy-load рдХрд░рдгреЗ, рдкреНрд░рддреНрдпреЗрдХ row рд╕рд╛рдареА рдПрдХ query, рд▓рдХреНрд╖рд╛рдд рди рдпреЗрддрд╛
- 100,000 values рдЕрд╕рд▓реЗрд▓реА рдПрдХ рдкреНрд░рдЪрдВрдб
INрдпрд╛рджреА тАФ рдХрд╛рд╣реАрд╢реЗ рдХрд┐рдВрд╡рд╛ рдХрд╛рд╣реА рд╣рдЬрд╛рд░рд╛рдВрдЪреНрдпрд╛ рддреБрдХрдбреНрдпрд╛рдВрдордзреНрдпреЗ batch рдХрд░рд╛ - "рд╕реБрд░рдХреНрд╖рд┐рддрддреЗрд╕рд╛рдареА" рдкреНрд░рддреНрдпреЗрдХ рдУрд│реАрдирдВрддрд░
flush(), рдЬреНрдпрд╛рдореБрд│реЗ buffering рдмрдВрдж рд╣реЛрддреЗ - pool рд╡рд╛рдкрд░рдгреНрдпрд╛рдРрд╡рдЬреА рдкреНрд░рддреНрдпреЗрдХ request рд╕рд╛рдареА рдирд╡реЗ database connection рдЙрдШрдбрдгреЗ
- рдлрдХреНрдд рддреБрдордЪреНрдпрд╛ code рдордзрд▓рд╛ рд╡реЗрд│ рдореЛрдЬрдгреЗ рдЖрдгрд┐ round trips рдЪреА рд╕рдВрдЦреНрдпрд╛ рди рдореЛрдЬрдгреЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ N+1 рдЯрд╛рд│рдгреНрдпрд╛рдЪреА ORM рдкрджреНрдзрдд. Django:
runners = Runner.objects.prefetch_related("laps") # 2 queries, not N+1
laps = Lap.objects.select_related("runner") # 1 query with a JOIN
from django.test.utils import CaptureQueriesContext # count queries in a test
SQLAlchemy:
from sqlalchemy.orm import selectinload
stmt = select(Runner).options(selectinload(Runner.laps)) # 1 + 1 queries, IN (...)
PostgreSQL pg_stat_statements extension рд╡рд╛рдкрд░реВрди рдкреНрд░рддреНрдпреЗрдХ query рдЖрдХрд╛рд░рд╛рд╕рд╛рдареА calls рдореЛрдЬрддреЛ тАФ рдкреНрд░рдЪрдВрдб calls рдЖрдгрд┐
рдЕрдЧрджреА рд▓рд╣рд╛рди mean_exec_time рдЕрд╕рд▓реЗрд▓реА query рдмрд▒реНрдпрд╛рдЪрджрд╛ N+1 рдЕрд╕рддреЗ:
SELECT calls, round(mean_exec_time::numeric, 2) AS mean_ms, query
FROM pg_stat_statements ORDER BY calls DESC LIMIT 5;
Bulk inserts: рдЕрдиреЗрдХ rows рд╕рд╛рдареА рдПрдХ statement, рдХрд┐рдВрд╡рд╛ PostgreSQL рдордзреНрдпреЗ COPY:
cur.executemany("INSERT INTO laps (runner_id, lap_ms) VALUES (%s, %s)", rows)
ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ: рддреБрдордЪреНрдпрд╛ tests рдордзреНрдпреЗ query counter рдареЗрд╡рд╛ (Django рдЪреЗ
assertNumQueries, рдХрд┐рдВрд╡рд╛ SQLAlchemy event listener) рдЖрдгрд┐ рдПрдЦрд╛рджреНрдпрд╛ page рдЪрд╛ count data рд╕реЛрдмрдд рд╡рд╛рдврд▓рд╛ рддрд░ build fail рдХрд░рд╛. рдзрдбрд╛ 12 рддреНрдпрд╛рдЪреЗ budget рдордзреНрдпреЗ рд░реВрдкрд╛рдВрддрд░ рдХрд░рддреЛ.
тПня╕П рдкреБрдвреЗ
рджреАрдкрд┐рдХрд╛ office рдХрдбреЗ рдЪрд╛рд▓рдд рдЧреЗрд▓реА рддреЗрд╡реНрд╣рд╛ рд╕рдВрдШрд╛рддрд▓реЗ рдмрд╛рдХреАрдЪреЗ рдиреБрд╕рддреЗ рдЙрднреЗ рд░рд╛рд╣рд┐рд▓реЗ. рд╡рд╛рдЯ рдкрд╛рд╣рддрд╛рдирд╛ рддреЗ рджреБрд╕рд░реЗ рдХрд╛рд╣реА рдХрд░реВ рд╢рдХрд▓реЗ рдЕрд╕рддреЗ рдХрд╛? рдкреБрдвреЗ: concurrency тАФ threads, asyncio, processes, рдЖрдгрд┐ GIL.
git checkout lesson-08-concurrency