Skip to content

Commit 985694c

Browse files
committed
Batch etsource cache reads with fetch_multi to reduce N+1 queries
MeritOrder collapses 6 individual cache fetches into one fetch_multi call. Molecules does the same for its 2 keys. Both use Thread.current to ensure fetch_multi runs at most once per request. Closes #1747
1 parent 4a999a0 commit 985694c

2 files changed

Lines changed: 41 additions & 11 deletions

File tree

app/models/etsource/merit_order.rb

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module Etsource
22
class MeritOrder
3+
CACHE_ATTRIBUTES = %i[merit_order hydrogen heat_network_lt heat_network_mt heat_network_ht agriculture_heat].freeze
4+
35
def initialize(etsource = Etsource::Base.instance)
46
@etsource = etsource
57
end
@@ -84,21 +86,34 @@ def import_agriculture_heat
8486
#
8587
# Returns a hash.
8688
def import(attribute)
87-
Rails.cache.fetch("#{attribute}_hash") do
88-
mo_nodes = Atlas::EnergyNode.all.select(&attribute).sort_by(&:key)
89-
mo_data = {}
89+
warm_cache!
90+
Thread.current[:merit_order_cache][attribute]
91+
end
92+
93+
# Batches all MeritOrder cache reads into one SQL query per request, so
94+
# subsequent fetch calls hit the LocalStore instead of the database.
95+
def warm_cache!
96+
cache_attr_by_key = CACHE_ATTRIBUTES.index_by { |a| "#{a}_hash" }
9097

91-
mo_nodes.each do |node|
92-
config = node.public_send(attribute)
93-
type = config.type.to_s
94-
group = config.group&.to_s
98+
Thread.current[:merit_order_cache] ||= Rails.cache.fetch_multi(*cache_attr_by_key.keys) do |cache_key|
99+
compute(cache_attr_by_key[cache_key])
100+
end.transform_keys { |k| cache_attr_by_key[k] }
101+
end
102+
103+
def compute(attribute)
104+
mo_nodes = Atlas::EnergyNode.all.select(&attribute).sort_by(&:key)
105+
mo_data = {}
95106

96-
mo_data[type] ||= {}
97-
mo_data[type][node.key.to_s] = group
98-
end
107+
mo_nodes.each do |node|
108+
config = node.public_send(attribute)
109+
type = config.type.to_s
110+
group = config.group&.to_s
99111

100-
mo_data
112+
mo_data[type] ||= {}
113+
mo_data[type][node.key.to_s] = group
101114
end
115+
116+
mo_data
102117
end
103118
end
104119
end

app/models/etsource/molecules.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module Etsource
44
# Loads data relating to the calculation of molecule flows based on the energy graph.
55
module Molecules
6+
CACHE_KEYS = %w[molecules.from_energy_keys molecules.from_molecules_keys].freeze
7+
68
module_function
79

810
# Internal: Computes the list of molecule graph nodes which have a molecule_conversion
@@ -12,6 +14,7 @@ module Molecules
1214
#
1315
# Returns an Array of Symbols.
1416
def from_energy_keys
17+
warm_cache!
1518
Rails.cache.fetch('molecules.from_energy_keys') do
1619
Atlas::MoleculeNode.all.select(&:from_energy).map(&:key).sort
1720
end
@@ -24,9 +27,21 @@ def from_energy_keys
2427
#
2528
# Returns an Array of Symbols.
2629
def from_molecules_keys
30+
warm_cache!
2731
Rails.cache.fetch('molecules.from_molecules_keys') do
2832
Atlas::EnergyNode.all.select(&:from_molecules).map(&:key).sort
2933
end
3034
end
35+
36+
# Batches all Molecules cache reads into one SQL query per request, so
37+
# subsequent fetch calls hit the LocalStore instead of the database.
38+
def warm_cache!
39+
Thread.current[:molecules_cache] ||= Rails.cache.fetch_multi(*CACHE_KEYS) do |key|
40+
case key
41+
when 'molecules.from_energy_keys' then Atlas::MoleculeNode.all.select(&:from_energy).map(&:key).sort
42+
when 'molecules.from_molecules_keys' then Atlas::EnergyNode.all.select(&:from_molecules).map(&:key).sort
43+
end
44+
end
45+
end
3146
end
3247
end

0 commit comments

Comments
 (0)