Skip to content

Rebuild missing SOLR content everywhere it is read, not just in term_info - #110

Merged
Robbie1977 merged 3 commits into
mainfrom
query-field-fallback
Sep 4, 2026
Merged

Rebuild missing SOLR content everywhere it is read, not just in term_info#110
Robbie1977 merged 3 commits into
mainfrom
query-field-fallback

Conversation

@Robbie1977

Copy link
Copy Markdown
Contributor

Rebuild missing SOLR content everywhere it is read, not just in term_info

#108 fixed term_info. It turns out four hydration paths had the same silent skip, and one of them was worse than "incomplete".

The connectivity queries were returning nothing

Not fewer rows — a confident empty answer. Before and after, live:

FBbt_00110929  alpha/beta core Kenyon cell         0 rows, count 0   →  236
FBbt_00048273  wing bristle mechanosensory neuron  0 rows, count 0   →  604

152 of 4,000 sampled connectivity classes are in that state. Someone asking what is downstream of a Kenyon cell was being told, with a straight face, nothing.

Four paths, one cause

Records that reach the PDB after the bulk indexer last completed have nothing in vfb_json, and precompute live query results has not completed since 22 June.

path reads did
_owlery_query_to_results anat_query, anat_image_query skipped the id — rows and count short
_fetch_connectivity_entries up/downstream_connectivity_query skipped the class — empty result
_bulk_fetch_per_instance_connectivity its own @with_solr_cache entries skipped, warning that counts were "a slight underestimate"
get_hierarchy term_info skipped — and lost the whole branch beneath the term

All four now collect what SOLR returned, work out what is missing, rebuild it, and carry on.

How the rebuilds work

The first two borrow the indexer's own query, same as #108 — nothing re-implements the schema. Measured on the live PDB a rebuild costs one round trip rather than per-id work (1 id 0.37s, 50 ids 0.91s, 200 ids 0.80s, 500 ids 1.49s, 3 ms/id), so ids go in batches of the indexer's own REQUEST_BATCH_SIZE. A set larger than one batch is looped, not truncated — dropping the tail would be the same quietly-short answer this PR exists to stop. Rebuilt payloads are returned in memory and used by the current request, because the write uses commitWithin 60s.

The per-instance connectivity miss is a different animal: not a gap in the bulk index but VFBquery's own result cache never having been asked for that instance. So the fix is to ask — get_neuron_neuron_connectivity is cache-wrapped, so computing one also fills the cache. Deliberately unbounded: an individual call should fix itself even if that call is slow. Misses are rare enough for that to be safe — 0 of 4,000 connectivity individuals sampled had none, and the case that surfaced it was 1 of 794.

#108 was serving but never indexing

solr_client builds its update URL from SOLRserver/SOLRcollection and a VFBquery deployment sets neither, so get_solr_update_url() returned None and send_solr_payload returned False without making a request. Confirmed against live 1.22.49: get_term_info("VFB_00107fob") served a complete document while id:VFB_00107fob stayed numFound 0 past the 60s commitWithin. Both are now seeded from VFBquery's own pysolr client.

The tests missed it because the one write test set VFBQUERY_CACHE_ENABLED=false to prove the guard, and that guard short-circuits before the URL is needed — the path that would have caught it was the path the test disabled.

Two tests that asserted content, not contract

term_info_serialization_dataset asserted exactly 4 types; DataSets gained stage labels and Ito2013 now has 5. test_default_excludes_are_a_strict_subset_of_everything demanded a strict decrease from excluding hemibrain and FAFB, which additionally requires those datasets to hold rows for that term pair — content, not contract. Both were failing on main.

Measured coverage

Population from each indexer's own get_parameters_query, coverage from SOLR:

anat_query                     51,848 expected   52,127 present    -279  (stale extras)
anat_image_query              635,685            632,476          3,209   0.5%
upstream_connectivity_query    12,960             12,671            289   2.2%
downstream_connectivity_query  12,960             12,694            266   2.1%
cluster_expression              3,290              3,290              0   0.0%

anat_query having 279 more documents than its population is stale entries for deprecated or removed classes. A fallback cannot fix that; only the bulk job clearing them can.

How to test

pytest src/test/test_term_info_fallback.py src/test/test_hierarchy.py — 54 pass, and the fallback tests need no backend. The example suite is unchanged at 15/15.

Scope note

The anat_query / anat_image_query rebuilds are wired but latent: the expansions those queries produce did not contain unindexed ids in any case I could construct, and get_instances / get_images_neurons return identically before and after. The demonstrated wins are the connectivity and hierarchy paths.

Three things, all downstream of the same cause: records that reach the PDB
after the bulk indexer last completed have nothing in vfb_json, and the
`precompute live query results` job has not completed since 22 June.

## The query indexes went missing as silently as term_info did

Both hydration paths skipped an id whose document lacked the field, so the
rows AND the count came back short with nothing to say so. For the
connectivity queries that is not "fewer rows" -- it is a confident empty
answer. Measured before and after on the live backend:

  FBbt_00110929  alpha/beta core Kenyon cell      0 rows -> 236
  FBbt_00048273  wing bristle mechanosensory      0 rows -> 604

152 of 4,000 sampled connectivity classes are in that state. A user asking
what is downstream of a Kenyon cell was being told, with a straight face,
nothing.

Both call sites now collect what SOLR returned, work out which requested
ids have no usable entry, and rebuild those in one batched call through
the indexer's own query -- the same borrowed-not-copied approach as
term_info. Measured on the live PDB a rebuild costs one round trip rather
than per-id work: 1 id 0.37s, 50 ids 0.91s, 200 ids 0.80s, 500 ids 1.49s
(3 ms/id). So the whole missing set goes at once, capped at the indexer's
own REQUEST_BATCH_SIZE of 500 purely to stop a pathological expansion
stalling. The rebuilt payloads are returned in memory and used by the
current request, because the indexer writes with commitWithin 60s and
re-reading SOLR here would still miss.

Global coverage measured against each indexer's own parameter query:
anat_image_query 0.5% missing, up/downstream connectivity ~2.1%,
cluster_expression 0%, anat_query has 279 MORE documents than the
population (stale entries a fallback cannot fix -- only the bulk job
clearing them can).

## The term_info fallback never actually indexed anything

solr_client builds its update URL from SOLRserver/SOLRcollection, and a
VFBquery deployment sets neither, so get_solr_update_url() returned None
and send_solr_payload returned False without making a request. Confirmed
against live 1.22.49: get_term_info("VFB_00107fob") served a complete
document while id:VFB_00107fob stayed numFound 0 past the 60s
commitWithin. Both are now seeded from VFBquery's own pysolr client
alongside the PDB variables.

The tests missed it because the one write test set VFBQUERY_CACHE_ENABLED
false to prove the guard, and that guard short-circuits before the URL is
needed -- the path that would have caught it was the path the test
disabled.

## Two tests that asserted content, not contract

term_info_serialization_dataset asserted exactly 4 types; DataSets gained
stage labels and Ito2013 now has 5 (Adult). It asserts the types it is
about instead.

test_default_excludes_are_a_strict_subset_of_everything demanded a strict
decrease from excluding hemibrain and FAFB, which additionally requires
those datasets to hold rows for that particular term pair -- content, not
contract. Now asserts subset, which is what excluding a dataset actually
guarantees.

Note on scope: the anat_query / anat_image_query rebuilds are wired but
latent. The expansions those queries produce did not contain unindexed ids
in any case I could construct -- get_instances and get_images_neurons are
unchanged before and after. The demonstrated win is the connectivity path.
_bulk_fetch_per_instance_connectivity skipped instances with no cached
result and warned that connected_n / pairwise_connections / total_weight
would be "a slight underestimate". That is a wrong number presented as a
real one.

This miss is a different animal to the others here: it is not a gap in the
bulk index but VFBquery's own @with_solr_cache never having been asked for
that instance. So the fix is simply to ask -- get_neuron_neuron_connectivity
is wrapped in the cache decorator, so computing a missing instance also
fills the cache and the next caller pays nothing.

Deliberately unbounded. An individual call should fix itself even if that
call is slow, rather than return a quietly incomplete count; misses are
rare enough for that to be safe (0 of 4,000 connectivity individuals
sampled had no cached result, and the case that surfaced this was 1 of
794 instances under FBbt_00048273).

Same principle applied to the query-index rebuild: BACKFILL_BATCH_SIZE
replaces MAX_BACKFILL_IDS, and a missing set larger than one batch is now
looped rather than truncated. Batching is what makes it cheap -- 500 ids
in 1.49s against 0.37s for one -- so there is no reason to drop the tail.

Verified live: FBbt_00048273 logs "Computing per-instance connectivity for
1 uncached instance(s)" and still returns its 604 rows.
…anch

Fourth hydration path with the same silent skip. get_hierarchy batch-reads
term_info from SOLR to work out each term's parents and did `continue` on a
document that was not there. That did not just lose the term's own node: it
lost everything beneath it, because nothing was ever appended to its
parent's child list, so an unindexed term took its whole branch out of the
tree with no error and no warning.

Collect the documents first, rebuild any that are missing through the
term_info fallback, then build the tree from the merged set.
@Robbie1977
Robbie1977 merged commit f1fa134 into main Sep 4, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant