On your PostgreSQL (psql recommended:
\timing on). Do not look at solutions.md before submitting.
Setup: generate volume — 20,000 seats, 5,000 reservations and items (a script or generate_series with INSERT...SELECT). Without volume there are no plans to read.
Exercise 1 — Without index vs with index
EXPLAIN ANALYZE SELECT * FROM events_payment WHERE reservation_id = 1234;(or your payments table). Seq Scan? Cost? Estimated vs actual rows?- Create the index
CREATE INDEX ON events_payment (reservation_id);and repeat. Note the improvement. - Now filter by something non-indexed with low selectivity (
WHERE status = 'SUCCEEDED'): seq scan or index? Why might the planner prefer the seq scan even with an index?
Exercise 2 — Reading the plan like a senior
- Run the availability query (07) with
EXPLAIN (ANALYZE, BUFFERS). - Identify: scan type per table, whether there is a Sort and with which method, estimated vs actual rows per node.
- Change the NOT EXISTS to LEFT JOIN + IS NULL and compare plans. Which one did the planner handle better?
Exercise 3 — The N+1, hunted and killed
- In the Django shell:
from django.db import connection, reset_queriesand count the queries of a loop printingr.user.emailfor 50 reservations. Note the number. - Same loop with
select_related("user"). How many queries? - Add
r.event.titleto the loop without touching the queryset: how many now? Fix it. - Bonus: reverse relation (
r.items.all()per reservation) with and withoutprefetch_related.
Exercise 4 — Design the right index
For each query, propose the index (columns and order) and justify:
WHERE event_id = X AND state = 'PUBLISHED' ORDER BY starts_at(event)WHERE gift_card_id = X ORDER BY created_at(lesson 08's ledger)WHERE user_id = X AND created_at > now() - interval '30 days'(reservation)WHERE code =?(coupon, looked up on every checkout — critical)WHERE status = 'PENDING_PAYMENT' AND expires_at < now()(expiration job — the hottest one)
Exercise 5 — The index that gets in the way
- Create an obviously useless index:
CREATE INDEX ON events_event (description);(long text, never filtered). - Measure INSERTs: before/after with
\timingand an INSERT...SELECT of 1,000 rows. How much does each insert pay to maintain the index? - Drop it (
DROP INDEX) and write down the rule you will apply before creating any index from now on.
Submit
Paste the trimmed plans and numbers. When corrected we close Lesson 09; next up Lesson 10 — Transactions.