Repository navigation
SRCH-6599 Cache OpenSearch document queries in Redis - #2177
Conversation
Repeat searches during traffic spikes should hit the searches cluster instead of OpenSearch, with a maintainer bypass and fail-open Redis timeouts.
Add a per-affiliate Super Admin toggle (search_cache_enabled, default off), gate caching on REDIS_CACHE_ON and REDIS_CACHE_DURATION, and rename the maintainer bypass to disable_search_cache (web and API v2). Key the cache on the full OpenSearch request so every param that changes the request changes the key. Only the user query is case-folded; site paths and filters keep their case, and timestamps are floored to the minute so relative ranges stay cacheable. Log diagnostics cache as hit, miss, bypass, or disabled, and add coverage specs that fail when a new search param is not classified.
0803055 to
98e4c67
Compare
jayandra
left a comment
There was a problem hiding this comment.
The changes LGTM and I was able to verify the caching behavior locally 🚀 I had a couple of comments about diagnostics module and default value for search_cache_enabled but both are non-blocking.
Also, should we add REDIS_CACHE_ON, REDIS_CACHE_DURATION and REDIS_CACHE_URL to .env.development, Thoughts?
===== NOTE TO SELF =====
There are three levers to enable/disable caching:
disable_search_cachequery paramsREDIS_CACHE_ONandREDIS_CACHE_DURATIONenv variablessearch_cache_enabledattribute in affiliates.
Steps taken to verify in localhost:
- Updated the cache store for
development.rbto beredis_cache_store. - Started sever by running:
REDIS_CACHE_ON=true REDIS_CACHE_DURATION=10 REDIS_CACHE_URL=redis://localhost:6379 bin/dev - Enabled affiliates
search_cache_enabledflag in http://localhost:3000/admin/affiliates - Navigated to http://localhost:3000/search?affiliate=va&query=diabetes
- Verified that reloading above page got its results from cache by looking for absence of
Query: *****block in rails log. - Verified the presence of
SRCHmodule (e.g.{"cached":true,"cache":"hit","module":"SRCH"})to keep track of cache hits/misses inimpressions.log:
| @@ -0,0 +1,5 @@ | |||
| class AddSearchCacheEnabledToAffiliates < ActiveRecord::Migration[7.1] | |||
| def change | |||
| add_column :affiliates, :search_cache_enabled, :boolean, default: false, null: false | |||
There was a problem hiding this comment.
Should this default to :true?
There was a problem hiding this comment.
Default for search cache should be NOT enabled. We will want to launch the feature with just one affiliate using the cache. Then, as we trust it, add more. At some point, a phase 3 would be enabling cache for all affilates.
| params = process_array_parameters(build_search_params).merge(indices: search_index) | ||
| document_search = OpenSearch::DocumentSearch.new(params, affiliate: @affiliate) | ||
| search_results = document_search.search | ||
| diagnostics['SRCH'] = { cached: document_search.cache_status == 'hit', cache: document_search.cache_status } |
There was a problem hiding this comment.
What is the full form for SRCH? Should we put the full form instead?
===NOTE TO SELF===
The diagnostics makes into impressions.log as "diagnostics":[{"cached":true|false,"cache":"hit|miss|disabled|bypass","module":"SRCH"}].
| :created_since, | ||
| :created_until, | ||
| :dc, | ||
| :disable_search_cache, |
There was a problem hiding this comment.
@luisgmetzger i thought we decided that we don't need this query param? right @jayandra @DK1999
| utf8 | ||
| redesign | ||
| include_facets | ||
| disable_search_cache |
There was a problem hiding this comment.
Yeah, we decided not to have a query param for disabling search cache. We're going to do it internally via toggle.
Yes I think adding those params to |
@luisgmetzger @jayandra is it normal to have to go in and edit that file to enable caching when working in development mode? Is there a way to make it respect the environment variable? Or is that not desirable for some reason? What about the test config file? Do we ever use that and should it be updated as well? |
@selfdanielj I do see value in updating the cache store to be redis in Currently, we should be able to run dev server and see everything working (besides the redis admin page) even while redis is not running. With the cache_store change, the server won't even start without a running redis service to connect to. |
I'm assuming we already rely on MySQL being present and both of those are started together in search-services by default |
Summary
searchesRedis (REDIS_CACHE_URL, namespacesearches) for 15 minutes so repeat queries skip the cluster.search_cache_enabledunder Enable/disable Settings. Defaults off for every site.REDIS_CACHE_ON(must betrue) andREDIS_CACHE_DURATION(minutes, default 15). Companion TF: searchgov-tf #353.disable_search_cache=true|1on web and API v2. It skips both read and write, and is excluded from the key and impression params. (The oldmemoryparam was never permitted on API v2; that is fixed.)searches:<affiliate_id>:v1:<sha256>over index, offset, size, and the full OpenSearch request body, so every param that changes the request changes the key. Only the user query is case-folded (uppercaseAND/OR/NOTkept). Site paths and filters keep their case. Timestamps are floored to the minute so "past hour" stays cacheable.diagnosticsnow logs{ module: "SRCH", cached: bool, cache: "hit"|"miss"|"bypass"|"disabled" }for Logstash and CloudWatch.docs/opensearch_query_cache.md(levers, bypass, per-site flush, rollout, rollback, known behavior).Rollout
engine.rb,production.rb,schema.rb); rebase once more before merge.prefix_lengthin Elasticsearch phrase suggester #353, then deploy so.envpicks up the new SSM params. A Puma restart alone does not.db:migratetreat every site as disabled (Affiliate#search_cache_enabled?guard).diagnostics.cachehit rate, OpenSearch query rate, p95 latency, anddgs-prod-searchesmemory/evictions.Rollback
Affiliate.update_all(search_cache_enabled: false).REDIS_CACHE_ON=falsein SSM and redeploy.Test plan
Automated (all passing locally, 592 examples across touched specs):
REDIS_CACHE_ON(unset/empty/false/yes/1/TRUE), affiliate flag, bypass values, duration (0, negative, non-numeric,015).ORvsor, quoted phrases, accented capitals, Turkish dotted I, site path case, missing offset, offsets/sizes/indices/affiliates, every filter option, timestamp flooring.searches_controller_cache_key_spec.rbandapi/v2/searches_controller_cache_key_spec.rbfail if a newly permitted param is not classified as changing the key or not reaching OpenSearch.delete_matchedflush against real Redis.error_handler.cachelabel and omits the bypass param.Manual (staging):
cache: hit; different case also hits.disable_search_cache=trueshowsbypassand writes no key.disabledimmediately.diagnostics.cacheis queryable in Logstash.rediss://(staging uses plainredis://).Findings and follow-ups (not in this PR)
tagsis permitted on web and API but never forwarded to OpenSearch (DocumentSearchable#facet_filter_hash). Pre-existing; separate ticket.dgs-prod-searchesusesvolatile-lru(fine; all keys have TTLs) and has no CloudWatch alarms. Propose Evictions, memory, and CPU alarms alongside searchgov-tf SRCH-779 - update validations to use \A instead of ^ #337.disable_search_cache.Checklist
Functionality Checks
You have merged the latest changes from the target branch (usually
main) into your branch.Your primary commit message is of the format SRCH-#### <description> matching the associated Jira ticket.
PR title is either of the format SRCH-#### <description> matching the associated Jira ticket (i.e. "SRCH-123 implement feature X"), or Release - SRCH-####, SRCH-####, SRCH-#### matching the Jira ticket numbers in the release.
Automated checks pass. If Code Climate checks do not pass, explain reason for failures:
Process Checks