Skip to content

Add bullet gem to catch n+1 queries #5961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Add bullet gem to catch n+1 queries #5961

wants to merge 16 commits into from

Conversation

squanto
Copy link
Contributor

@squanto squanto commented Apr 25, 2025

Link to pivotal/JIRA issue

Is PM acceptance required? (delete one)

  • No - merge after code review approval

Reminder: merge main into this branch and get green tests before merging to main

What was done?

How to test?

  • verify test coverage
  • ensure existing changes don't change behavior
  • write an n+1 query to double check that it fails the build with bullet enabled

squanto added 4 commits April 25, 2025 12:47
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Copy link

Heroku app: https://gyr-review-app-5961-6480d40149ad.herokuapp.com/
View logs: heroku logs --app gyr-review-app-5961 (optionally add --tail)

squanto added 5 commits April 25, 2025 12:57
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
@squanto squanto changed the title [draft] Add bullet gem [draft] Add bullet gem2 Apr 25, 2025
squanto added 3 commits April 25, 2025 14:25
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
squanto added 3 commits April 25, 2025 16:23
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
@squanto squanto changed the title [draft] Add bullet gem2 Add bullet gem to catch n+1 queries Apr 28, 2025
@squanto squanto marked this pull request as ready for review April 28, 2025 14:45
Co-authored-by: Hugo Melo <hmelo@codeforamerica.org>
@squanto squanto requested a review from Copilot May 5, 2025 17:12
Copy link

@Copilot Copilot AI left a comment

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 adds the bullet gem to catch n+1 queries, addresses various errors in the code, and then disables the gem for future use. Key changes include adding eager loading to various ActiveRecord queries to optimize database access and modifying or removing unnecessary includes.

Reviewed Changes

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

Show a summary per file
File Description
app/views/shared/_tax_return_list.html.erb Added eager loading for assigned_user to tax_returns
app/services/search_indexer.rb Removed extra eager loads, now only eager loading tax_returns
app/presenters/message_presenter.rb Added includes for documents on incoming messages
app/models/client.rb Updated eager loading within scopes
app/lib/client_sorter.rb Eager loads parent organization on vita partner queries
app/helpers/role_helper.rb Adjusted user role loading with eager-loading queries
app/controllers/* Adjusted several controller queries to include necessary associations and avoid n+1 queries
Gemfile Added bullet gem to the development and test groups
Comments suppressed due to low confidence (2)

app/services/search_indexer.rb:34

  • The removal of includes for documents and intake: [:dependents] may lead to additional queries if the corresponding associations are accessed later. Consider re-adding these associations if they are used downstream in the mapping.
attributes = Client.includes(:tax_returns).where(id: client_ids).map do |client|

app/controllers/portal/tax_returns_controller.rb:45

  • Removing the includes for client and intake may result in additional queries if these associations are accessed later. Verify that this change does not cause unintended N+1 query issues in related views or logic.
@tax_return = current_client.tax_returns.find_by(id: params[:tax_return_id])

@@ -25,9 +25,5 @@ def filter_cookie_name
def ensure_always_current_user_assigned
@always_current_user_assigned = true
end

def load_vita_partners
@vita_partners = VitaPartner.accessible_by(current_ability)
Copy link
Contributor

Choose a reason for hiding this comment

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

does this utilize the application_controller method?

Copy link
Contributor

@arinchoi03 arinchoi03 left a comment

Choose a reason for hiding this comment

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

some q's -- also possibly a boulder on the test.rb set up

end

def load_independent_organizations
@independent_organizations = Organization.where(coalition: nil).where.not(id: @independent_org_srts.pluck(:target_id))
@independent_organizations = Organization.where(coalition: nil).where.not(id: @independent_org_srts.map(&:target_id))
Copy link
Contributor

Choose a reason for hiding this comment

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

can you explain why this change from pluck to map? Is pluck making a new sql query whereas we already have the @independent_org_srts at this point (already fetched from db) so we want to avoid using pluck and instead iterate over the objects already in-memory?

@@ -42,7 +42,7 @@ def show; end
private

def load_client_tax_return
@tax_return = current_client.tax_returns.includes(client: [:intake]).find_by(id: params[:tax_return_id])
Copy link
Contributor

Choose a reason for hiding this comment

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

why was this removed in this case? Is it b/c this success page (which is the only page that utilizes load_client_tax_return method does not need client/intake records

sites.map(&:name).join(", ")
elsif user.role_type == TeamMemberRole::TYPE
sites = TeamMemberRole.find(user.role_id).sites
sites.map(&:name).join(", ")
Copy link
Contributor

Choose a reason for hiding this comment

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

can you explain why these sets of changes were necessary?

@@ -115,7 +115,7 @@ def self.sortable_intake_attributes
delegate *delegated_intake_attributes, to: :intake
scope :after_consent, -> { where.not(consented_to_service_at: nil) }
scope :assigned_to, ->(user) { joins(:tax_returns).where({ tax_returns: { assigned_user_id: user } }).distinct }
scope :with_eager_loaded_associations, -> { includes(:vita_partner, :tax_returns, :documents, tax_returns: [:assigned_user]) }
scope :with_eager_loaded_associations, -> { includes(:vita_partner, :documents, intake: [:dependents], tax_returns: [:assigned_user]) }
Copy link
Contributor

Choose a reason for hiding this comment

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

where are the dependents records used?

@@ -31,7 +31,7 @@ def self.refresh_filterable_properties(client_ids = nil, limit = 1000)
Client.where(id: client_ids)
end

attributes = Client.where(id: client_ids).includes(:tax_returns, :documents, intake: :dependents).map do |client|
attributes = Client.includes(:tax_returns).where(id: client_ids).map do |client|
Copy link
Contributor

Choose a reason for hiding this comment

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

dont we look up documents & intake for each client, which is why we include(:tax_returns, :documents, :intake), not sure about the dependents piece?

@@ -6,6 +6,15 @@
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
config.after_initialize do
Bullet.enable = false
Copy link
Contributor

Choose a reason for hiding this comment

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

doesn't this completely just turn off the bullet gem & therefore it won't have a chance to raise?

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.

2 participants