Skip to content

Query optimizations#36

Open
akx wants to merge 5 commits into
masterfrom
opt
Open

Query optimizations#36
akx wants to merge 5 commits into
masterfrom
opt

Conversation

@akx

@akx akx commented Jun 14, 2026

Copy link
Copy Markdown
Member

SQL queries

Path Before After
Single reserve 27 6
Scatter (36) 165 62
Form render (6 zones) ~18 4

akx and others added 4 commits June 14, 2026 15:51
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>
@akx akx requested review from Copilot, japsu and shadikka June 14, 2026 12:51
@akx akx added the enhancement New feature or request label Jun 14, 2026
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.91%. Comparing base (9dd28fb) to head (8ff4d77).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread paikkala/models/programs.py
Comment thread paikkala/models/tickets.py
- 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>
@akx akx marked this pull request as ready for review June 14, 2026 13:12
@akx akx requested a review from Copilot June 14, 2026 14:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants