Skip to content

Enhancement: Performance optimization for Kingdom, Park, and Attendance page loads#458

Open
baltinerdist wants to merge 1 commit into
amtgard:masterfrom
baltinerdist:feature/refactoring-04-11
Open

Enhancement: Performance optimization for Kingdom, Park, and Attendance page loads#458
baltinerdist wants to merge 1 commit into
amtgard:masterfrom
baltinerdist:feature/refactoring-04-11

Conversation

@baltinerdist

Copy link
Copy Markdown
Contributor

Summary

  • 9 new composite DB indexes on the most-scanned tables (ork_attendance, ork_park, ork_mundane, ork_event_calendardetail, ork_event_rsvp) plus removal of 2 now-redundant indexes
  • N+1 query elimination on Kingdom and Park event listings — RSVP counts went from 2 correlated subqueries per event to 1 pre-aggregated JOIN
  • Attendance write optimizationAddAttendance() partition columns (date_year, date_month, date_week3, date_week6) now computed before INSERT, eliminating a post-INSERT UPDATE; SetAttendance() also fixed (previously left partition columns stale when the date changed)
  • Adjacent park date query merged from 2 round-trips into 1 UNION ALL
  • Navigation unblockingsession_write_close() on all Kingdom AJAX endpoints releases the PHP session file lock immediately, so clicking away from a Kingdom page is never blocked by slow in-flight stats queries; AbortController on the client cancels both fetches on navigation and handles bfcache restore correctly

Details

Database indexes (db-migrations/2026-04-11-performance-indexes.sql)

The Kingdom profile page fires several queries that scan ork_attendance over 26–52 week windows and group by computed columns (date_week3, date_month). Without covering indexes the planner falls back to full table scans on a 3.5M+ row table.

New indexes:

Table Index Purpose
ork_attendance (park_id, date, mundane_id) Park roster & 6-month scans
ork_attendance (kingdom_id, date, mundane_id, park_id) Kingdom 26/52-week covering index
ork_attendance (date_year, date_week3, mundane_id) Weekly GROUP BY
ork_attendance (date_year, date_month, mundane_id) Monthly GROUP BY
ork_attendance (mundane_id, park_id, date) Per-player park lookups
ork_park (kingdom_id, active) Park/parkday filtering
ork_mundane (park_id, suspended, active) Roster member filtering
ork_event_calendardetail (event_id, event_start) Event date range filtering
ork_event_rsvp (event_calendardetail_id, status) RSVP count queries

Dropped (now redundant prefixes of wider covering indexes): idx_park_date, idx_attendance_kingdom_date_mundane. Migration uses CREATE/DROP INDEX IF NOT EXISTS throughout — safe to re-run.

N+1 RSVP fix (controller.Kingdom.php, controller.Park.php)

Before: Every event in the listing fired 2 correlated subqueries against ork_event_rsvp (one for going, one for interested). With 20 events on screen: 41 queries total for RSVP data.

After: A single pre-aggregated LEFT JOIN subquery computes all RSVP counts in one pass. 20 events: 1 query.

Attendance partition columns (system/lib/ork3/class.Attendance.php)

Added _computeDatePartitions() private helper that calls SELECT YEAR(), MONTH(), WEEK(date,3), WEEK(date,6) directly against MariaDB (required for WEEK mode 6 year-boundary correctness — no PHP formula is reliable for all edge cases). Called from both AddAttendance() (before save(), eliminating the post-INSERT UPDATE) and SetAttendance() (fixing a pre-existing gap where date edits left partition columns stale).

Adjacent park date query (orkui/model/model.Attendance.php)

get_adjacent_park_dates() replaced two sequential DataSet() calls with one UNION ALL query.

Navigation unblocking (controller.Kingdom.php, Kingdomnew_index.tpl)

PHP file-based sessions use exclusive locks. A slow park_averages_json request holds the lock for its entire query duration, blocking any subsequent page load from the same browser. session_write_close() at the top of each JSON endpoint releases the lock immediately (these methods never write to the session). On the client, an AbortController wired to pagehide cancels both in-flight fetches on navigation; a pageshow handler restores fetch capability on bfcache back/forward without leaving the stats columns stuck on spinners.

Test plan

  • Run migration: docker exec -i ork3-php8-db mariadb -u root -proot ork < db-migrations/2026-04-11-performance-indexes.sql
  • Verify SHOW INDEX FROM ork_attendance shows the 5 new indexes and does not show idx_park_date or idx_attendance_kingdom_date_mundane
  • Load a Kingdom profile — park stat columns (avg/wk, avg/mo, total players, total members) should populate; clicking a nav link mid-load should navigate immediately without waiting
  • Load a Park profile — event RSVP counts present; roster loads
  • Edit an existing attendance record's date — verify date_year/date_month/date_week3/date_week6 update correctly in the DB row
  • Add a new attendance record — verify partition columns are correct in the inserted row
  • Kingdom profile → press Back in browser → park stats should re-populate (bfcache restore path)

🤖 Generated with Claude Code

…ce page loads

## Database indexes (db-migrations/2026-04-11-performance-indexes.sql)
- Add 9 composite indexes on ork_attendance, ork_park, ork_mundane,
  ork_event_calendardetail, and ork_event_rsvp targeting the heaviest
  query patterns (26/52-week kingdom scans, park roster lookups,
  weekly/monthly GROUP BY on computed columns, RSVP counts)
- Drop 2 now-redundant indexes (idx_park_date, idx_attendance_kingdom_date_mundane)
  that are strict prefixes of newly added covering indexes

## N+1 query fix — Kingdom and Park event listings
- Replace correlated subqueries for RSVP going/interested counts with a
  single pre-aggregated LEFT JOIN subquery; reduces 2N+1 queries to 1
  for any event listing (controller.Kingdom.php, controller.Park.php)

## Attendance partition columns — eliminate post-INSERT UPDATE
- Add _computeDatePartitions() private helper in class.Attendance.php
  that computes date_year/month/week3/week6 via a direct MariaDB call
  (ensures WEEK(date,6) year-boundary correctness vs. any PHP formula)
- Call helper from both AddAttendance() and SetAttendance() before save(),
  eliminating the post-INSERT UPDATE and fixing a pre-existing gap where
  SetAttendance() left partition columns stale on date edits

## Adjacent park date query — 2 queries to 1
- Merge two separate DataSet() calls in get_adjacent_park_dates() into
  a single UNION ALL query (model.Attendance.php)

## Navigation unblocking — Kingdom page
- Add session_write_close() as first line of park_monthly_json(),
  park_averages_json(), and players_json() in controller.Kingdom.php;
  releases the PHP session file lock immediately so navigating away
  from a Kingdom page is never blocked by slow in-flight AJAX queries
- Add AbortController in Kingdomnew_index.tpl wired to pagehide so
  both fetch() calls are cancelled the moment the user navigates away;
  pageshow handler with e.persisted guard re-creates the controller and
  re-triggers the averages fetch on bfcache restore

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant