From 193b1fa5e31118f8ed8f222ebbc5a632e06c41a7 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Mon, 3 Jun 2024 11:45:51 -0700 Subject: [PATCH] Improve performance for `occupies:X` searches I noticed in the app that these queries were slowwww! I was able to track it down to a bad query plan, as we explain in the comment. I searched online for "mysql query performance filter on one join table sort by another", and was surprised to find this answer suggest a subquery, which I've often been told to expect to be slower compared to joins? But it certainly worked in this case! https://stackoverflow.com/questions/35679693/mysql-optimize-join-with-filter-and-order-on-different-tables --- app/models/item.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/models/item.rb b/app/models/item.rb index c58a4150..3ec79c43 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -60,8 +60,23 @@ class Item < ApplicationRecord } scope :occupies, ->(zone_label) { zone_ids = Zone.matching_label(zone_label).map(&:id) + + # NOTE: In searches, this query performs much better using a subquery + # instead of joins! This is because, in the joins case, filtering by an + # `swf_assets` field but sorting by an `items` field causes the query + # planner to only be able to use an index for *one* of them. In this case, + # MySQL can use the `swf_assets`.`zone_id` index to get the item IDs for + # the subquery, then use the `items`.`name` index to sort them. + i = arel_table + psa = ParentSwfAssetRelationship.arel_table sa = SwfAsset.arel_table - joins(:swf_assets).where(sa[:zone_id].in(zone_ids)).distinct + where( + ParentSwfAssetRelationship.joins(:swf_asset). + where(sa[:zone_id].in(zone_ids)). + where(psa[:parent_type].eq("Item")). + where(psa[:parent_id].eq(i[:id])). + arel.exists + ) } scope :not_occupies, ->(zone_label) { zone_ids = Zone.matching_label(zone_label).map(&:id)