Konrad du Plessis 61c485ffcf perf(payroll): batch project-loop N+1s + quick-wins pass closing summary
Profiled /payroll/ under Django Debug Toolbar and confirmed heavy N+1
patterns in the shared payroll_dashboard() code path (shared by all four
tabs). Main wins:

1. outstanding_project_costs loop + project_chart_data loop previously
   fired one PayrollAdjustment SELECT per project (outstanding) and per
   (project x 6 months) (chart) — ~42+7 = 49 round-trips on a 7-project
   dataset. Replaced with 4 GROUP BY aggregate queries keyed by
   project_id / (project_id, month), merged in Python.

2. Per-worker Loan.exists() and get_worker_active_team() checks inside
   the workers_data loop — pre-computed into a set + dict once, up-front.

3. team_workers_map loop used `team.workers.filter(active=True)` which
   bypasses the prefetch cache; switched to a Prefetch(to_attr=) that
   returns already-filtered active workers, dropping 6 duplicate SELECTs.

4. Adjustments tab: reused `paginator.count` for the "Total" stat card
   (was firing a second identical COUNT(*)) and reused existing
   all_workers / all_teams querysets instead of re-querying for the
   filter popovers.

5. Hoisted shared lookups (all_workers, active_projects_list, chart
   date-window) so duplicate ordering-identical SELECTs from multiple
   call sites collapse into a single evaluated queryset.

===== Quick-Wins Pass A - before/after query counts =====
  /                            15q, no duplicates (healthy, no fix)
  /payroll/?status=pending     157q (before) -> 26q (after), 0 dupes
  /payroll/?status=history     157q          -> 26q,         0 dupes
  /payroll/?status=loans       158q          -> 27q,         0 dupes
  /payroll/?status=adjustments 168q          -> 34q,         0 dupes

CSS cache-bust token (0c42cde) is still expected to be the biggest
user-felt improvement of this pass — custom.css now holds at
Cloudflare's edge for its full 4h TTL instead of being re-fetched
from the VM on every page load. The payroll-dashboard query-count
cut (~131 SQL round-trips trimmed per render) is a meaningful
admin-UX latency win on top of that, especially under MySQL over
the Flatlogic network.

WeasyPrint confirmed still lazy-imported.
Test suite: 68/68.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 01:16:37 +02:00
..
2026-02-22 12:14:54 +00:00
2026-04-22 00:19:15 +02:00
2026-02-22 12:14:54 +00:00
2026-04-22 00:19:15 +02:00
2026-04-22 00:19:15 +02:00