Skip to content

fix: homepage performance - missing indexes and inefficient queries#2506

Merged
olleolleolle merged 2 commits intocodebar:masterfrom
mroderick:fix/homepage-performance
Feb 24, 2026
Merged

fix: homepage performance - missing indexes and inefficient queries#2506
olleolleolle merged 2 commits intocodebar:masterfrom
mroderick:fix/homepage-performance

Conversation

@mroderick
Copy link
Collaborator

Summary

Fixes the 503 timeout on the homepage (Heroku 30s timeout) caused by performance issues introduced in #2503.

Root Cause Analysis

Problem 1: Missing Database Indexes

The code changes in #2503 introduced new methods that query invitations filtered by attending: true:

# Member#attending_event_ids makes 3 queries:
invitations.accepted.pluck(:event_id)
workshop_invitations.accepted.pluck(:workshop_id)
meeting_invitations.accepted.pluck(:meeting_id)

The accepted scope filters on the attending column, but there were no indexes on (member_id, attending) or (event_id, attending), causing full table scans.

Problem 2: Inefficient total_upcoming_events_count

# BEFORE: Loads ALL workshops with full eager loading just to count
def total_upcoming_events_count
  workshops = Workshop.eager_load(:chapter, :sponsors, :organisers, :permissions)
  all_events(workshops.to_a).count  # Loads EVERYTHING, then counts
end

This loaded ALL workshops in the database with expensive JOINs (including polymorphic permissions) just to get a count.

Problem 3: Presenters Bypass Eager Loading

The dashboard queries workshops with eager_load(:permissions), but the presenters ignored this and made their own queries:

# EventPresenter#organisers - bypasses eager loading
def organisers
  model.permissions.find_by(name: 'organiser')&.members || []
end

This triggered additional queries per event, defeating the purpose of eager loading.

Fixes Applied

1. Database Migration

Added composite indexes for invitation queries:

add_index :workshop_invitations, [:member_id, :attending]
add_index :workshop_invitations, [:workshop_id, :attending]
add_index :meeting_invitations, [:member_id, :attending]
add_index :meeting_invitations, [:meeting_id, :attending]
add_index :invitations, [:member_id, :attending]
add_index :invitations, [:event_id, :attending]

2. Optimized Event Counter

# AFTER: Simple COUNT queries
def total_upcoming_events_count
  workshop_count = Workshop.upcoming.count
  event_count = Event.upcoming.count
  meeting_exists = Meeting.next.present? ? 1 : 0
  workshop_count + event_count + meeting_exists
end

3. Use Eager-Loaded Associations

# EventPresenter#organisers - now uses pre-loaded data
def organisers
  @organisers ||= model.organisers.to_a
end

The presenters now use the model.organisers association (which uses the eager-loaded permissions) instead of making additional queries.

Testing

  • All existing tests pass (77 presenter tests, 8 homepage feature tests)
  • Migration applied successfully to test database

Deployment

Run the migration on production:

bundle exec rails db:migrate

- Add composite indexes on (member_id, attending) and (event_id, attending)
  for workshop_invitations, meeting_invitations, and invitations tables
- Replace inefficient total_upcoming_events_count that loaded all workshops
  with simple COUNT queries
- EventPresenter#organisers now uses model.organisers instead of
  model.permissions.find_by which bypasses eager loading
- WorkshopPresenter#organisers uses model.organisers with fallback to
  chapter_organisers for backwards compatibility
Copy link
Collaborator

@olleolleolle olleolleolle left a comment

Choose a reason for hiding this comment

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

Nice to only count those! And to get the presenters fixed!

@olleolleolle olleolleolle merged commit ed6e150 into codebar:master Feb 24, 2026
7 checks passed
@mroderick mroderick deleted the fix/homepage-performance branch February 24, 2026 13:40
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.

3 participants