Skip to main content

Search and discovery

Search is a separate service, vidra-search, and the most important thing about it is what happens when it is not there: core falls back to its own SQL search. Search never takes the site with it.

What it does

CapabilityHow
Hybrid searchFull-text plus trigram matching, so a query finds both the phrase and the near-miss spelling
AutosuggestTypo-tolerant completions as you type
TrendingDecayed counters, so yesterday's spike stops outranking today's
RecommendationsCo-visitation — what else people who watched this watched
Learned rankingA LightGBM ranker, shadow-evaluated online before anyone activates it manually

The ranker is the part worth reading twice: it is trained and then run in shadow against live traffic, scoring queries without affecting what anyone sees, until an operator decides the numbers justify switching it on. It is never enabled by a training run.

It is internal, and it stays internal

vidra-search is called only by vidra-core, over the compose network, HMAC-authenticated with a shared SEARCH_INTERNAL_SECRET. Its contract lives at vidra-search/api/openapi.yaml, entirely under /internal/v1.

Do not add a Caddyfile site for it and do not publish its port past the host — the production overlay removes the publish entirely. Its host port (SEARCH_HTTP_PORT, default 8081) exists for local inspection in development.

Wiring it in production

The explicit -f chain a production deploy uses disables the automatic merge of docker-compose.override.yml — which is intended, but that override file is also the only place the api's search wiring is set in development. In production you must set both values in the env file:

SEARCH_SERVICE_URL=http://search:8080
SEARCH_INTERNAL_SECRET=<openssl rand -hex 32> # must equal the search service's INTERNAL_SECRET

The two failure modes are asymmetric, and only one of them is loud:

  • Leave SEARCH_SERVICE_URL empty and the whole search integration is disabled. The site degrades gracefully to local SQL search with no error anywhere — exactly the kind of silence that survives to production.
  • Set the URL without the secret and validate() requires at least 32 characters and the api refuses to boot.

vidra doctor has a search service check for the first case.

Rotating the shared secret

SEARCH_INTERNAL_SECRET must change on both services in one deploy. It is a shared HMAC: one ${SEARCH_INTERNAL_SECRET} substitution feeds the api's variable and the search service's INTERNAL_SECRET, so a single env-file edit plus one up -d is atomic enough. A split rollout means every core-to-search call 401s until it converges.

One database, one dump

vidra-search shares the core database in the search schema, so the nightly database-wide dump already includes it. It has its own migration ledger (public.vidra_search_migrations) and its own migrator one-shot; either ledger can go dirty without the other, which is why deploy.sh runs the two migrators as separate gated steps.