Add cached fields to Item model for searching, but don't use them yet

This is the first part of a change to improve search performance, by
caching occupied zone IDs and supported body IDs onto the Item record
itself, instead of always doing joins with `SwfAsset`.

It's unfortunate, because part of the power of SQL is joins! But doing
joins with big tables, in ways that can't take advantage of indexes in
the same ways as we often want to, is… slow.

It's possible there's something I'm misunderstanding about SQL
optimization, and this _could_ be done with query optimization or
indexes instead of duplicating data like this? This complexity carries
the risk of data getting out of sync in unforeseen ways. But this is
what I know how to do, and it seems to be working, so! Okay!
This commit is contained in:
Emi Matchu 2024-09-30 23:10:37 -07:00
parent 4a431a4ae8
commit efda6d74ab
4 changed files with 34 additions and 3 deletions

View file

@ -296,6 +296,12 @@ class Item < ApplicationRecord
restricted_zones + occupied_zones restricted_zones + occupied_zones
end end
def update_cached_fields
self.cached_occupied_zone_ids = occupied_zone_ids.sort.join(",")
self.cached_compatible_body_ids = compatible_body_ids.sort.join(",")
self.save!
end
def species_support_ids def species_support_ids
@species_support_ids_array ||= read_attribute('species_support_ids').split(',').map(&:to_i) rescue nil @species_support_ids_array ||= read_attribute('species_support_ids').split(',').map(&:to_i) rescue nil
end end
@ -305,7 +311,7 @@ class Item < ApplicationRecord
replacement = replacement.join(',') if replacement.is_a?(Array) replacement = replacement.join(',') if replacement.is_a?(Array)
write_attribute('species_support_ids', replacement) write_attribute('species_support_ids', replacement)
end end
def support_species?(species) def support_species?(species)
species_support_ids.blank? || species_support_ids.include?(species.id) species_support_ids.blank? || species_support_ids.include?(species.id)
end end

View file

@ -4,6 +4,9 @@ class ParentSwfAssetRelationship < ApplicationRecord
belongs_to :parent, :polymorphic => true belongs_to :parent, :polymorphic => true
belongs_to :swf_asset belongs_to :swf_asset
after_save :update_parent_cached_fields
after_destroy :update_parent_cached_fields
def item=(replacement) def item=(replacement)
self.parent = replacement self.parent = replacement
@ -16,4 +19,8 @@ class ParentSwfAssetRelationship < ApplicationRecord
def pet_state=(replacement) def pet_state=(replacement)
self.parent = replacement self.parent = replacement
end end
def update_parent_cached_fields
parent.try(:update_cached_fields)
end
end end

View file

@ -0,0 +1,16 @@
class AddCachedFieldsToItems < ActiveRecord::Migration[7.2]
def change
add_column :items, :cached_occupied_zone_ids, :string, null: false, default: ""
add_column :items, :cached_compatible_body_ids, :text, null: false, default: ""
reversible do |direction|
direction.up do
puts "Updating cached item fields for all items…"
Item.includes(:swf_assets).find_in_batches.with_index do |items, batch|
puts "Updating item batch ##{batch+1}"
items.each(&:update_cached_fields)
end
end
end
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2024_09_28_022359) do ActiveRecord::Schema[7.2].define(version: 2024_10_01_052510) do
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t|
t.integer "species_id", null: false t.integer "species_id", null: false
t.integer "color_id", null: false t.integer "color_id", null: false
@ -137,6 +137,8 @@ ActiveRecord::Schema[7.2].define(version: 2024_09_28_022359) do
t.text "description", size: :medium, null: false t.text "description", size: :medium, null: false
t.string "rarity", default: "", null: false t.string "rarity", default: "", null: false
t.integer "dyeworks_base_item_id" t.integer "dyeworks_base_item_id"
t.string "cached_occupied_zone_ids", default: "", null: false
t.text "cached_compatible_body_ids", default: "", null: false
t.index ["dyeworks_base_item_id"], name: "index_items_on_dyeworks_base_item_id" t.index ["dyeworks_base_item_id"], name: "index_items_on_dyeworks_base_item_id"
t.index ["modeling_status_hint", "created_at", "id"], name: "items_modeling_status_hint_and_created_at_and_id" t.index ["modeling_status_hint", "created_at", "id"], name: "items_modeling_status_hint_and_created_at_and_id"
t.index ["modeling_status_hint", "created_at"], name: "items_modeling_status_hint_and_created_at" t.index ["modeling_status_hint", "created_at"], name: "items_modeling_status_hint_and_created_at"
@ -154,7 +156,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_09_28_022359) do
end end
create_table "modeling_logs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| create_table "modeling_logs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t|
t.datetime "created_at", precision: nil, default: -> { "CURRENT_TIMESTAMP" }, null: false t.datetime "created_at", precision: nil, default: -> { "current_timestamp()" }, null: false
t.text "log_json", size: :long, null: false t.text "log_json", size: :long, null: false
t.string "pet_name", limit: 128, null: false t.string "pet_name", limit: 128, null: false
end end