Skip to content

Fix Rack::Timeout on CheckoutController#show from N+1 queries#4295

Open
gumclaw wants to merge 1 commit intomainfrom
fix/checkout-timeout-n-plus-1-queries
Open

Fix Rack::Timeout on CheckoutController#show from N+1 queries#4295
gumclaw wants to merge 1 commit intomainfrom
fix/checkout-timeout-n-plus-1-queries

Conversation

@gumclaw
Copy link
Copy Markdown
Contributor

@gumclaw gumclaw commented Apr 2, 2026

What

Fixes Rack::Timeout::RequestTimeoutException (120s timeout) on CheckoutController#show caused by three compounding N+1 query issues:

  1. CheckoutPresenter#purchases loaded ALL user purchases into memory — for each purchase, it triggered separate queries for .link and .variant_attributes.first. Replaced with a single pluck(:link_id, base_variant_id) query returning an ID-pair Set for O(1) lookups.

  2. CartPresenter created a new CheckoutPresenter per cart product — the memoized @_purchases was never shared across items, so the expensive purchases query ran once per cart product. Now a single presenter is shared across all items.

  3. No eager loading on cart_products — each cart product triggered lazy loads for product, option, affiliate, and accepted_offer. Added includes(:product, :option, :affiliate, accepted_offer: :offer_code).

Why

Users with many purchases (and carts with multiple products) could hit the 120s Rack::Timeout. The root cause is the purchases method loading every purchase record with N+1 associations, multiplied by the number of cart products since each got its own presenter instance.

The fix replaces O(P × N) AR instantiations (P = purchases, N = cart products) with a single lightweight query returning only ID pairs.

Test Results

  • All 43 presenter tests pass (41 existing + 2 new)
  • All 20 checkout controller tests pass
  • New tests verify cross-sell and upsell variant filtering when buyer has prior purchases

Generated with Claude Opus 4.6. Prompted with the Sentry issue for the Rack::Timeout::RequestTimeoutException on CheckoutController#show and asked to investigate and fix the N+1 query bottlenecks.

@slavingia slavingia requested a review from ershad April 8, 2026 14:09
@gumclaw gumclaw force-pushed the fix/checkout-timeout-n-plus-1-queries branch from 4402da0 to 3ac4915 Compare April 8, 2026 14:21
Replace full-table purchases load with a single ID-pair query, share one
CheckoutPresenter across cart products, and eager-load cart_product
associations to eliminate repeated queries per item.
@gumclaw gumclaw force-pushed the fix/checkout-timeout-n-plus-1-queries branch from 3ac4915 to d299642 Compare April 8, 2026 19:25
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