Skip to content

Match search operators against assetsSummary instead of per-asset metadata - #2882

Open
bendichter wants to merge 2 commits into
masterfrom
search-operators-use-assets-summary
Open

Match search operators against assetsSummary instead of per-asset metadata#2882
bendichter wants to merge 2 commits into
masterfrom
search-operators-use-assets-summary

Conversation

@bendichter

@bendichter bendichter commented Aug 5, 2026

Copy link
Copy Markdown
Member

Problem

The advanced search operators released recently are slow. Timed against production:

query time
species:mouse created_after:2026-01-01 14.7s
species:mouse 13.4s
created_after:2026-01-01 0.26s
mouse (free text) 0.63s

All of the cost is in species:, approach:, and technique:. Those matched per-asset metadata in the asset_search materialized view with jsonb_path_exists and a jsonpath built at runtime. A runtime-built jsonpath regex cannot use any of the view's GIN indexes, so each query sequential-scanned one row per asset and evaluated a regex against every row's metadata blob. The visible_to() embargo check added a correlated subquery per row on top of that. A comment in filters.py had flagged this risk when the operators were written.

Change

species, approach, and technique now match the version-level assetsSummary aggregation, which already rolls up exactly these three fields. The scan moves from one row per asset to one row per dandiset version, three to four orders of magnitude fewer rows. This is the same source the standard: operator was pointed at in a232b1f for a related reason.

While in here:

  • Re-added standard:, which was removed in 05fda3c pending a decision on the right metadata source. That decision is now made, and it matches assetsSummary.dataStandard.
  • Removed file_type:. Its remaining value is covered by standard:, and dropping it takes AssetSearch out of this code path entirely. Note this is only the search operator; the faceted-search file_type query parameter on DandisetSearchQueryParameterSerializer is a separate feature and is untouched.
  • apply_search_filters no longer takes the requesting user. Visibility is enforced by the view's base queryset (get_visible_dandisets), and these filters only ever narrow it, so the per-row embargo subquery was redundant. The embargo test is retained and still passes.

One consequence to be aware of: assetsSummary is recomputed during metadata aggregation rather than on every asset write, so results can lag slightly for a dandiset whose assets changed very recently.

I have not re-measured latency against production data, since that requires the change to be deployed. The structural argument is the row-count reduction described above.

🤖 Generated with Claude Code

The species, approach, and technique operators matched per-asset metadata
in the asset_search materialized view using jsonb_path_exists with a
jsonpath built at runtime. That predicate cannot use any of the view's
GIN indexes, so every such query sequential-scanned one row per asset and
evaluated a regex against each row's metadata blob. Measured against
production, `species:mouse` took 13.4 seconds while the date operators,
which filter the Dandiset table directly, took 0.26 seconds.

These three fields are already rolled up per version in
`assetsSummary`, which is where the previously removed `standard:`
operator was eventually pointed for the same reason. Match all of them
against the version metadata instead. The scan is now one row per
dandiset version rather than one row per asset, which is three to four
orders of magnitude fewer rows.

This changes the cross-key semantics from per-asset AND to dandiset-level
AND: `species:mouse approach:electrophysiological` now matches a dandiset
whose summary lists both, even if no single asset has both. For a
dandiset listing search that is the more useful reading, and the tests
were updated to pin it.

Re-add `standard:`, which was removed in 05fda3c pending a decision on
the right metadata source, and drop `file_type:`, whose remaining value
is covered by `standard:`. Removing it takes AssetSearch out of this code
path entirely, so `apply_search_filters` no longer needs the requesting
user: visibility was already enforced by the view's base queryset, and
these filters only narrow it. The faceted-search `file_type` parameter on
DandisetSearchQueryParameterSerializer is a separate feature and is
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bendichter

bendichter commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

demo on local seed data:

Screen.Recording.2026-08-05.at.3.33.30.PM.mov

@bendichter
bendichter marked this pull request as ready for review August 5, 2026 19:36
`variableMeasured` records the neurodata types present in a dandiset's
assets (ElectricalSeries, Units, LFP, ImagingPlane, and so on). Sampling
the archive, it is populated on roughly 77% of dandisets with 22 distinct
values, which makes it the most useful remaining field in assetsSummary.
It also answers a different question than the operators we already have:
approach and technique describe how an experiment was conducted, while
this describes what data structures are actually in the files.

Unlike the other summary arrays, `variableMeasured` holds bare strings
rather than objects with a `name`, so its jsonpath selects the array
elements themselves. That is the only structural difference, so the
lookup table now holds full paths and `_jsonpath_name_match` is renamed
to `_jsonpath_match` to reflect that it no longer always matches a name.

Named `variable:` rather than `variable_measured:` to follow the existing
convention, where `technique:` comes from `measurementTechnique` and
`standard:` from `dataStandard`. Underscores are used only by the date
operators, where the suffix distinguishes before from after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bendichter

Copy link
Copy Markdown
Member Author

Added a variable: operator over assetsSummary.variableMeasured in 86dffab.

I surveyed the archive before picking this one. Of the fields left in assetsSummary, variableMeasured is populated on about 77% of dandisets and carries 22 distinct values in my sample, led by ProcessingModule (39), ElectrodeGroup (20), OpticalChannel (17), ImagingPlane (17), Units (15), and ElectricalSeries (14), with SpatialSeries, LFP, PupilTracking, and EyeTracking further down. It answers a genuinely different question from the operators we already have: approach: and technique: describe how the experiment was run, while this describes what is actually in the files, so "which dandisets contain sorted units" or "which have eye tracking" becomes answerable.

The one structural difference is that variableMeasured is an array of bare strings rather than objects with a name, so its jsonpath selects the elements themselves. The lookup table now holds full paths and _jsonpath_name_match became _jsonpath_match, since it no longer always matches a name.

On naming: I went with variable: rather than variable_measured: to match the existing convention, where technique: comes from measurementTechnique and standard: from dataStandard. Underscores show up only in the date operators, where the suffix is doing real work distinguishing before from after. Happy to rename if you would rather be explicit; it is a one-line change in the parser plus the frontend list.

For the record, the fields I looked at and skipped: numberOfSamples is populated on 8% of dandisets and numberOfCells on none of the 100 I sampled, so operators over either would return almost nothing. numberOfBytes and numberOfFiles are always present, but a size filter already exists in the faceted sidebar. numberOfSubjects is populated on 93% and is worth having, but it is a threshold operator rather than a substring match and is already written up as num_subjects: on the unmerged advanced-search-counts branch, so it belongs in its own PR.

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