Conversation
The multi-zone scatter change made every reservation compute the full
reservation status of *every* zone in the program just to recover the
total reserved ticket count, then discard the rest. This made each
reservation O(number of zones) in DB queries and CPU.
- Replace the all-zones status sum with a single tickets.count(): the
per-zone total_reserved sum is exactly the program's total ticket count.
- Add select_related('zone') in get_reservation_status so row.reserve()
doesn't issue a query per row to read row.zone.
On the Sibeliustalo fixture (6 zones), a single-ticket reservation drops
from 27 to 8 queries and no longer scales with zone count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three more N+1 levers on top of the O(zones) fix: - get_reservation_status() now fetches the actual reserved seat numbers (one query per zone) and exposes them as reserved_set. Row.reserve() accepts these, so it no longer re-queries taken numbers per row. - All rows of a zone share a single Zone instance, so reading row.zone costs no query and the zone's seat qualifiers are prefetched once per zone instead of once per created ticket; recompute_qualifiers() filters them in Python. - Row.reserve() bulk_creates the row's tickets in a single INSERT instead of one per seat (Ticket.prepare_save() populates key/qualifier cache that bulk_create would otherwise skip). Measured query counts on the Sibeliustalo / scatter fixtures: single-ticket, single zone 27 -> 7 6-ticket batch, single zone 18 -> 7 36-ticket multi-zone scatter 165 -> 63 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reserve() counted the program's tickets once for check_reservable() and again for the quota check. Count once and pass it into check_reservable(), which now accepts an optional precomputed total_reserved. Drops the fixed per-request query count from 7 to 6 (single-ticket and small-batch reservations alike). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Optimizations for the form-render and program-listing paths, complementing the reserve() allocation-path work: - Program.get_reservation_statuses(): compute several zones' reservation status in a constant number of queries instead of ~3 per zone in a loop. The reservation form now renders in ~4 queries regardless of zone count (was ~3 x number of zones). - ProgramQuerySet.with_ticket_counts() + annotated remaining_tickets: avoid an N+1 COUNT when listing many programs. - ReservationForm: skip computing per-zone "seats remaining" labels on bound (POST) forms, where the widget isn't re-rendered. - ReservationForm: jittered retry backoff so colliding requests don't wake in lockstep and web workers are freed faster during a signup stampede. reserved_set on RowReservationStatus defaults to an empty set, since the batched display path only needs counts (the allocation path still populates it for the actual seat numbers). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #36 +/- ##
==========================================
+ Coverage 91.04% 91.91% +0.87%
==========================================
Files 48 48
Lines 983 1027 +44
==========================================
+ Hits 895 944 +49
+ Misses 88 83 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR targets reducing database query counts in the reservation flow and reservation form rendering by batching status computation, reusing already-computed reservation data, and avoiding per-ticket qualifier queries.
Changes:
- Add a batched
Program.get_reservation_statuses()API and use it in the zone choice field to reduce per-zone query overhead. - Optimize reservation allocation by passing per-row reserved seat sets into
Row.reserve(), bulk-creating tickets, and prefetched seat qualifiers. - Reduce work on POSTs by skipping per-zone “remaining seats” computation when the reservation form is bound; add a query-count regression test for single-zone reservations.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| paikkala/models/zones.py | Extend row reservation status to optionally carry per-row reserved seat sets for allocation without extra queries. |
| paikkala/models/tickets.py | Introduce prepare_save() and adjust qualifier recomputation to leverage prefetched qualifiers. |
| paikkala/models/rows.py | Allow passing known reserved numbers, prefetch qualifiers, and bulk-create tickets to cut query count. |
| paikkala/models/programs.py | Add queryset annotation helper, batched reservation status computation, and reduce reservation-path counting work. |
| paikkala/forms.py | Skip widget-only reservation status computation on bound forms; jitter retry backoff on IntegrityError. |
| paikkala/fields.py | Switch reservation status population to the new batched program API. |
| paikkala_tests/test_paikkala.py | Add regression test asserting single-zone reservation query count does not scale with zone count. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- test_with_ticket_counts_annotation: remaining_tickets reads the annotated num_tickets without an extra COUNT, and falls back to a COUNT when absent. - test_bound_form_skips_reservation_status: a bound (POST) ReservationForm skips per-zone reservation-status computation, so zone labels fall back to str(zone); an unbound (GET) form still computes them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SQL queries