You know that feeling when you open a page and it seems to be loading straight from the last decade? Well... that's exactly what we were experiencing on our product page. One of the bottlenecks was the stock verification for each product variant. This was being done synchronously, meaning the page would only load after checking the stock for each variant. Asynchronous Separation The first improvement was structural. I made this part of the page asynchronous, using turbo_frame_tag with src and loading: :lazy. This allowed the main content to render quickly, while the stock data and product selection form loaded separately, enhancing the user's perception of speed. Misleading Abstractions Even with asynchronous loading, the requests were still heavy. I investigated and found we were doing this: product_variant.total_available_quantity It seemed harmless... until I saw what was behind it: def total_available_quantity Factory.total_available_quantity(finished_sku: sku) end Digging deeper, I found this: class Factory # ... def self.total_available_quantity(finished_sku:) stock_skus = SkuMapper.where(finished_sku: finished_sku, enabled: true).pluck(:stock_sku) FactoryStock.where(stock_sku: stock_skus, enabled: true).sum(:available_quantity) end # ... end In other words, for each variant, we were making at least two queries—one to SkuMapper and another to FactoryStock—to sum up the stock. Imagine this running on a product with 15 or 25 variants... Anticipate and Map Instead of repeating these queries, I fetched all the finished_sku for the variants and then created two hashes with the anticipated data: The first maps the variant's finished_sku to the stock_sku from SkuMapper: { finished_sku: stock_sku } The second maps the stock_sku from FactoryStock to its available quantity: { stock_sku: available_quantity } With this, we consolidated the queries and transformed what were multiple where clauses into simple key lookups in the hash. Result On the product page, we saw a reduction of nearly 85% in response time, going from 983.7 ms to 141.7 ms. Additionally, we reduced the number of queries from 206 to 37. Before and after in NewRelic: And on the page now responsible for delivering all this information for users to choose their product, we achieved 149.2 ms with just 9 SQL queries. How the new page looks in NewRelic:

Apr 11, 2025 - 03:48
 0

You know that feeling when you open a page and it seems to be loading straight from the last decade? Well... that's exactly what we were experiencing on our product page.

One of the bottlenecks was the stock verification for each product variant. This was being done synchronously, meaning the page would only load after checking the stock for each variant.

Asynchronous Separation

The first improvement was structural. I made this part of the page asynchronous, using turbo_frame_tag with src and loading: :lazy. This allowed the main content to render quickly, while the stock data and product selection form loaded separately, enhancing the user's perception of speed.

Misleading Abstractions

Even with asynchronous loading, the requests were still heavy. I investigated and found we were doing this:

product_variant.total_available_quantity

It seemed harmless... until I saw what was behind it:

def total_available_quantity
  Factory.total_available_quantity(finished_sku: sku)
end

Digging deeper, I found this:

class Factory
  # ...
  def self.total_available_quantity(finished_sku:)
    stock_skus = SkuMapper.where(finished_sku: finished_sku, enabled: true).pluck(:stock_sku)
    FactoryStock.where(stock_sku: stock_skus, enabled: true).sum(:available_quantity)
  end
  # ...
end

In other words, for each variant, we were making at least two queries—one to SkuMapper and another to FactoryStock—to sum up the stock. Imagine this running on a product with 15 or 25 variants...

Anticipate and Map

Instead of repeating these queries, I fetched all the finished_sku for the variants and then created two hashes with the anticipated data:

  • The first maps the variant's finished_sku to the stock_sku from SkuMapper:
{
  finished_sku: stock_sku
}
  • The second maps the stock_sku from FactoryStock to its available quantity:
{
  stock_sku: available_quantity
}

With this, we consolidated the queries and transformed what were multiple where clauses into simple key lookups in the hash.

Result

On the product page, we saw a reduction of nearly 85% in response time, going from 983.7 ms to 141.7 ms. Additionally, we reduced the number of queries from 206 to 37.

Result

Before and after in NewRelic:

Before

After

And on the page now responsible for delivering all this information for users to choose their product, we achieved 149.2 ms with just 9 SQL queries.

result

How the new page looks in NewRelic:

new page looks in NewRelic