Skip to content

Commit a695553

Browse files
committed
Cache Primo queries
Why these changes are being introduced: The Primo API performs poorly. We should do what we can to mitigate this. Relevant ticket(s): * [USE-76](https://mitlibraries.atlassian.net/browse/USE-76) How this addresses that need: This adds caching to Primo queries. Side effects of this change: None, but it's worth noting that we experience many unique searches, and this will only help with latency if USE UI has seen the query before.
1 parent a4922a2 commit a695553

File tree

1 file changed

+40
-29
lines changed

1 file changed

+40
-29
lines changed

app/controllers/search_controller.rb

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ def results
1717

1818
# Determine which tab to load - default to primo unless gdt is enabled
1919
@active_tab = if Flipflop.enabled?(:gdt)
20-
'gdt' # Keep existing GDT behavior unchanged
20+
'gdt' # Keep existing GDT behavior unchanged
2121
else
22-
params[:tab] || 'primo' # Default to primo for new tabbed interface
22+
params[:tab] || 'primo' # Default to primo for new tabbed interface
2323
end
2424
@enhanced_query = Enhancer.new(params).enhanced_query
2525

@@ -53,25 +53,23 @@ def load_gdt_results
5353
end
5454

5555
def load_primo_results
56-
begin
57-
primo_search = PrimoSearch.new
58-
per_page = params[:per_page] || 20
59-
primo_response = primo_search.search(params[:q], per_page)
60-
61-
@results = NormalizePrimoResults.new(primo_response, params[:q]).normalize
62-
63-
# Basic pagination for now.
64-
if @results.present?
65-
@pagination = {
66-
hits: @results.count,
67-
start: 1,
68-
end: @results.count
69-
}
70-
end
71-
72-
rescue StandardError => e
73-
@errors = handle_primo_errors(e)
56+
primo_response = cache_primo_query
57+
@results = NormalizePrimoResults.new(primo_response, @enhanced_query[:q]).normalize
58+
59+
# Enhanced pagination using cached response
60+
if @results.present?
61+
total_hits = primo_response.dig('info', 'total') || @results.count
62+
per_page = @enhanced_query[:per_page] || 20
63+
current_page = @enhanced_query[:page] || 1
64+
65+
@pagination = {
66+
hits: total_hits,
67+
start: ((current_page - 1) * per_page) + 1,
68+
end: [current_page * per_page, total_hits].min
69+
}
7470
end
71+
rescue StandardError => e
72+
@errors = handle_primo_errors(e)
7573
end
7674

7775
def load_timdex_results
@@ -88,16 +86,9 @@ def active_filters
8886
end
8987

9088
def cache_timdex_query(query)
91-
# Create cache key for this query
92-
# Sorting query hash to ensure consistent key generation regardless of the parameter order
93-
sorted_query = query.sort_by { |k, v| k.to_sym }.to_h
94-
cache_key = Digest::MD5.hexdigest(sorted_query.to_s)
89+
cache_key = generate_cache_key(query)
9590

9691
# builder hands off to wrapper which returns raw results here
97-
# We are using two difference caches to allow for Geo and USE to be cached separately. This ensures we don't have
98-
# cache key collision for these two different query types. In practice, the likelihood of this happening is low,
99-
# as the query parameters are different for each type and they won't often be run with the same cache backend other
100-
# than locally, but this is a safeguard.
10192
# The response type is a GraphQL::Client::Response, which is not directly serializable, so we convert it to a hash.
10293
Rails.cache.fetch("#{cache_key}/#{@active_tab}", expires_in: 12.hours) do
10394
raw = if @active_tab == 'gdt'
@@ -112,6 +103,26 @@ def cache_timdex_query(query)
112103
end
113104
end
114105

106+
def cache_primo_query
107+
cache_key = generate_cache_key(@enhanced_query)
108+
109+
Rails.cache.fetch("#{cache_key}/primo", expires_in: 12.hours) do
110+
primo_search = PrimoSearch.new
111+
per_page = @enhanced_query[:per_page] || 20
112+
primo_search.search(@enhanced_query[:q], per_page)
113+
end
114+
end
115+
116+
# We are using two difference caches to allow for Geo and USE to be cached separately. This ensures we don't have
117+
# cache key collision for these two different query types. In practice, the likelihood of this happening is low,
118+
# as the query parameters are different for each type and they won't often be run with the same cache backend other
119+
# than locally, but this is a safeguard.
120+
def generate_cache_key(query)
121+
# Sorting query hash to ensure consistent key generation regardless of the parameter order
122+
sorted_query = query.sort_by { |k, _v| k.to_sym }.to_h
123+
Digest::MD5.hexdigest(sorted_query.to_s)
124+
end
125+
115126
def execute_geospatial_query(query)
116127
if query['geobox'] == 'true' && query[:geodistance] == 'true'
117128
TimdexBase::Client.query(TimdexSearch::AllQuery, variables: query)
@@ -261,7 +272,7 @@ def validate_geobox_values!
261272

262273
def handle_primo_errors(error)
263274
Rails.logger.error("Primo search error: #{error.message}")
264-
275+
265276
if error.is_a?(ArgumentError)
266277
[{ 'message' => 'Primo search is not properly configured.' }]
267278
elsif error.is_a?(HTTP::TimeoutError)

0 commit comments

Comments
 (0)