@@ -111,6 +111,35 @@ Note: data reads in `vfb_queries.py` (term_info, painted domains, ontology
111111label lookups, etc.) still go to ` solr.virtualflybrain.org ` — only the result
112112* cache* moved. The two are independent.
113113
114+ ## Cache versioning and invalidation
115+
116+ Every cache entry is stamped with the VFBquery package version (major.minor) that
117+ wrote it, so results from an old code version aren't served after an upgrade.
118+
119+ The ** running** version is resolved (in ` solr_result_cache.py ` ) as:
120+
121+ 1 . the ` VFBQUERY_VERSION ` environment variable if set, otherwise
122+ 2 . the installed package version (` importlib.metadata.version('vfbquery') ` ),
123+
124+ normalized to ** major.minor** . That value comes from the single source of truth,
125+ ` src/vfbquery/_version.py ` (see [ RELEASING.md] ( RELEASING.md ) ).
126+
127+ On read, if an entry's stamp differs from the running version, invalidation is
128+ ** monotonic** — it only discards entries written by an * older* version:
129+
130+ - ** Older (or unversioned) entry** → invalidated, deleted, and recomputed by the
131+ current code.
132+ - ** Newer entry** (seen by a stale/older install, or by an older deploy running
133+ alongside a newer one) → treated as a miss but ** not deleted** . An older client
134+ must never purge a fresher entry; the previous ` != ` check did, which let
135+ downgrades wipe live entries and made concurrent versions thrash each other.
136+
137+ Consequences for the major.minor namespace:
138+
139+ - ** Patch bumps** (` 1.20.0 → 1.20.3 ` ) share the cache — no invalidation.
140+ - ** Minor/major bumps** (` 1.20 → 1.21 ` ) invalidate older entries on read, so a
141+ release that changes query output naturally refreshes the cache.
142+
114143## Runtime Configuration
115144
116145Control caching behavior:
@@ -133,6 +162,54 @@ Disable caching globally if needed:
133162export VFBQUERY_CACHE_ENABLED=false
134163```
135164
165+ When disabled, the cache layer is ** fully bypassed** — every query runs live
166+ against Neo4j/Owlery/Solr with ** no read, no write, no version-invalidation, and
167+ no contact with the cache server** (` solr_caching_disabled() ` in
168+ ` solr_result_cache.py ` ; mirrored in ` vfb_connectivity.query_connectivity ` ).
169+
170+ This is how the ** integration tests** run in CI. The test steps that assert on
171+ query * results* (` test_neuron_neuron_connectivity ` , ` test_neuron_region_connectivity ` ,
172+ ` test_vfb_connectivity ` , the unit tests in ` python-test.yml ` , and ` examples.yml ` )
173+ set ` VFBQUERY_CACHE_ENABLED=false ` so they:
174+
175+ - validate the ** live** query for the branch under test, not a (possibly stale)
176+ cached result, and
177+ - never write a PR/branch's output back into the ** shared production cache** .
178+
179+ The performance workflow's perf-timing steps keep caching enabled on purpose
180+ (they measure warm-cache latency); only the result-asserting steps disable it.
181+
182+ #### Read-only mode
183+
184+ ``` bash
185+ export VFBQUERY_CACHE_READONLY=true
186+ ```
187+
188+ Read-only mode still ** reads** the cache (warm results are served), but
189+ suppresses every ** mutation** — no writes, no force-refresh clears, and no
190+ version/expiry purges (` solr_caching_readonly() ` , gating ` cache_result ` ,
191+ ` clear_cache_entry ` and ` _clear_expired_cache_document ` ).
192+
193+ This is used by the ** performance-test workflow's perf-timing steps** , but only
194+ on ** pull requests** — ` VFBQUERY_CACHE_READONLY ` is set from
195+ ` github.event_name == 'pull_request' ` . So:
196+
197+ - ** On PRs** the perf steps read warm entries for representative timings but
198+ never write or purge. Combined with ` VFBQUERY_CACHE_ENABLED=false ` on the
199+ result-asserting steps, ** no PR run can modify the production cache** .
200+ - ** On push-to-` main ` and scheduled runs** those perf steps are * writable* , so
201+ they refresh/warm the cache under the current ` main ` version.
202+
203+ That post-merge + daily-scheduled warming (plus lazy refresh by production
204+ traffic) is what keeps the cache populated for the version on ` main ` , including
205+ after a release bumps it. There's no dedicated release-triggered warm.
206+
207+ Caveat: a PR that bumps the ** minor/major** version reads cold in read-only mode
208+ (its version's entries don't exist yet — see version invalidation below);
209+ same-version PRs read the already-warm production entries. If you'd rather PR
210+ checks read * and* write a cache without touching production, point them at a
211+ separate collection with ` VFBQUERY_SOLR_URL ` instead.
212+
136213## Performance Benefits
137214
138215VFBquery SOLR caching provides significant performance improvements:
0 commit comments