Key Vespa Architecture Questions: Ranking, Deployment & Queries

Hello all, we’re implementing Vespa for a production search system migrating from Solr, and have several architectural questions that we couldn’t find clear answers for in the documentation. We’re particularly interested in understanding Vespa’s flexibility around ranking and query execution compared to Solr’s dynamic capabilities.


Questions:

1. External Rank Profiles
Is there support for external rank profiles or ranking configuration files? We need to modify ranking logic frequently without full application redeployment. Can rank profiles be defined in separate files that are loaded dynamically, or updated via API?

2. Query-Time Ranking Definition
Can ranking expressions be passed in queries instead of pre-defined in schemas? We want to define ranking logic at query execution time for A/B testing without schema changes. Is there support for inline ranking expressions or parameterized rank profiles?

3. Deployment Impact on Search
How does Vespa handle search requests during application deployment? Is there any query disruption, partial availability, or performance degradation? What’s the recommended approach for zero-downtime ranking updates?

4. Relevance Score with Filters Only
When using only attribute filters (e.g., where category = 'X' and price > 100), why do results have relevance scores > 0? What calculation produces this score when no text matching is involved?

5. Sort by Relevance Ascending
How can we sort search results by lowest relevance first? The default sorts highest-first. order by relevance asc fails with “no sortable attribute.” Are there workarounds to get least relevant results?

6. Grouping Order Logic
For grouping queries with | all(group(field)), in what order are groups returned? Is it by frequency, field value, or document order in original query? Can this order be controlled?

7. Dynamic Field Selection for userQuery()
Can we specify which fields userQuery() searches at query time? Currently it uses schema/query-profile defaults. We need Solr-like qf parameter functionality to dynamically change searched fields per query.


For those who have made similar migrations from Solr/Elasticsearch to Vespa, we’d especially like to know about your experiences with these specific challenges. Any references to documentation, benchmarks, or real-world experiences would be greatly appreciated!

1 Like

For now:


Context: why these questions feel “harder” in Vespa than in Solr

Solr encourages request-time dynamism: field lists (qf), boosts, and sometimes scoring tweaks can be pushed into query params. Vespa is designed around a different contract:

  • Ranking logic lives in the application package as rank profiles and is compiled and validated as part of deployment. (VespaDocument)
  • Request-time flexibility is mostly “select a pre-defined profile” + “pass inputs into that profile.” (VespaDocument)
  • This yields predictable latency and safety (invalid changes rejected before activation), but you must engineer experimentation differently than Solr. (VespaDocument)

With that framing, here are direct answers and the production patterns that map closest to your Solr expectations.


1) External rank profiles. Separate files? Dynamically loaded? Updated by API?

What Vespa supports

Yes, external ranking config files exist, but they are not “dynamic runtime plugins.”

  • You can define rank profiles either inline in the schema or in separate files:
    schemas/<schema-name>/<profile-name>.profile. (VespaDocument)
  • Rank profiles can also inherit from each other to reduce duplication. (VespaDocument)

This solves “giant schema file” and supports clean modularity.

What Vespa does not support

No, you cannot hot-load or update rank profiles via query-time API calls.
Rank expressions are stored in a rank profile, and rank profiles are part of the deployed config. (VespaDocument)

The production workaround that gives Solr-like iteration speed

Engineer rank profiles as templates plus query-time inputs:

  • Deploy stable rank profiles infrequently.
  • Change weights and thresholds frequently via input.query(...) or other query features. The Query API explicitly supports passing input data used in ranking. (VespaDocument)
  • For “structural” changes (new features, new phases, new expression graph), deploy. Deployments are designed to be safe under live traffic. (VespaDocument)

2) Query-time ranking definition. Can we send ranking expressions in the query?

Direct answer

No. You cannot pass an arbitrary ranking expression in a request and have Vespa execute it.
This is a known limitation and is commonly explained as an efficiency/compilation issue. (Stack Overflow)

Related: even some deeper rank-feature configuration knobs (example: bm25 properties) are explicitly “rank-profile only,” not request-settable. (GitHub)

What you do instead for A/B testing

You get two practical degrees of freedom that cover most experimentation needs:

  1. Choose among pre-defined rank profiles at query time
    ranking.profile=<name> (often abbreviated as ranking=<name>). Vespa docs describe this selection model. (VespaDocument)

  2. Parameterize rank profiles
    Define expressions that read query features and set them per request:

  • input.query(w_title)=2.0
  • input.query(w_body)=0.5
  • etc. The Query API guide shows query inputs as a first-class part of requests. (VespaDocument)

High-leverage pattern: pre-deploy a small family of profiles (baseline, expA, expB) and then vary weights via query inputs. This matches how Vespa teams describe running experiments without redeploying. (Medium)


3) Deployment impact on search. Any disruption? How to do zero-downtime ranking updates?

What Vespa promises (and generally delivers)

Vespa documentation is explicit:

  • Deploying application package changes is generally safe at any time.
  • It does not disrupt queries and writes.
  • Invalid or destructive changes are rejected before taking effect. (VespaDocument)

Vespa Cloud adds orchestrated multi-zone deployments and CD hooks. (VespaDocument)

The real operational nuance

Even when requests are not dropped, some changes can increase load:

  • Schema/indexing changes that trigger reindexing.
  • Changes that increase ranking cost per hit or increase rerank-count in phased ranking. (VespaDocument)

So, “no disruption” is about availability. “No performance change” depends on what you deployed.

Recommended approach for zero-downtime ranking iteration

Use a two-lane strategy:

  • Lane A: No-deploy iteration
    Keep rank profile structure stable. Change weights via query inputs. (VespaDocument)

  • Lane B: Safe deploy iteration
    When structure must change, deploy new rank profile names (or inherited variants), then switch traffic gradually by selecting ranking.profile per request. (VespaDocument)

This maps closely to how teams run experiments: predefine multiple profiles and select them in the query. (Medium)


4) Relevance score with filters only. Why is relevance > 0 with no text match?

Background: “relevance” is just the output of the active rank profile

In Vespa, each hit’s relevance is the rank score computed by the chosen rank profile. (VespaDocument)

If you do not specify a profile, Vespa uses:

  • default if present in your schema
  • otherwise a builtin default ranking by nativeRank. (VespaDocument)

Why filter-only queries still show non-zero

Because rank expressions can be query-independent or mostly query-independent:

  • A rank profile can use document features only (attributes), producing a non-zero score even without text matching. Vespa’s ranking intro shows an example where the relevance equals attribute(inlinks).count, explicitly noting that the expression does not use query features. (VespaDocument)

Also note a subtle pitfall:

  • Even if you override ordering with order by, Vespa still computes the rank score unless you choose unranked. The YQL reference states Vespa does not optimize away rank-score computation just because you discard it via sorting. (VespaDocument)

If you truly want “no ranking” for filter endpoints

Use the built-in rank profile unranked:

  • ranking.profile=unranked
    This is explicitly recommended for optimal performance of sorting queries. (VespaDocument)

How to debug “where did this score come from?”

Turn on rank feature listing for debugging and offline analysis:

  • ranking.listFeatures=true is documented in the ML tutorial as a way to return rank features. (VespaDocument)

5) Sort by relevance ascending. How to get “least relevant first”?

Direct answer

Use the special sorting attribute '[relevance]' and sort ascending:

  • order by '[relevance]' asc (VespaDocument)
  • Sorting reference notes [relevance] defaults to descending if you omit direction, so you must be explicit. (VespaDocument)

Example YQL:

select * from mydoc where userQuery() order by '[relevance]' asc;

If you want stable results when many documents have equal relevance, add a deterministic secondary sort (for example on an id attribute). The YQL reference recommends secondary ordering after '[relevance]'. (VespaDocument)


6) Grouping order logic. What order do groups come back in? Can we control it?

Default group order

If you do not specify order(...) in the grouping expression, default ordering is:

  • -max(relevance()) (descending by the best hit’s relevance per group). (VespaDocument)

If your query is effectively “all documents” or relevance ties across groups, order will look random. The grouping guide explicitly calls this out. (VespaDocument)

How to control group order

Add order(...) at the group level. The grouping guide and reference both emphasize:

  • order(...) controls group ordering, not hit ordering inside each group. (VespaDocument)
  • Use aggregators like count(), avg(), sum() for deterministic ordering after merges. The reference notes limitations for max/min ordering after merge operations. (VespaDocument)

Practical examples:

  • Most frequent groups first: order(-count())
  • Least frequent first: order(count())
  • Order by a numeric attribute aggregate: order(-max(attribute(price)))

Important operational constraint

Fields used in grouping must be attributes. The Query API docs state this explicitly. (VespaDocument)


7) Dynamic field selection for userQuery(). Solr qf equivalent?

What Vespa does by default

If you do not specify fields in the query, Vespa searches a fieldset named default (or a field named default), if you defined one. The text search tutorial describes this and shows a fieldset default { fields: ... }. (VespaDocument)

How to choose fields at query time

You typically do this by switching the default searched field or fieldset:

  • Use model.defaultIndex to specify a field or fieldset if not using the default one. The YQL reference explicitly points to model.defaultIndex for this purpose. (VespaDocument)

So the Vespa version of Solr qf is usually:

  • Predefine a handful of fieldsets (qf_short, qf_long, qf_title_only, …)
  • Choose among them per request via model.defaultIndex=<fieldset> (VespaDocument)

Where boosts belong in Vespa

In Solr, boosts often live in qf. In Vespa, boosts usually live in ranking:

  • weight bm25(title) vs bm25(body) etc using query inputs.
  • That keeps query parsing stable and puts tuning where Vespa expects it. (VespaDocument)

How to keep this safe at scale

Use query profiles to provide defaults and reduce client complexity, and query profile types to restrict what parameters are legal (typed, optionally “no extra params”). Vespa docs describe query profile types explicitly. (VespaDocument)


Migration reality check: what teams report when moving from Solr or Elasticsearch

If your system looks like “production commerce search with lots of filters, facets, and frequent tuning,” two public migrations are especially relevant:

  • Vinted describes a multi-stage migration from Elasticsearch to Vespa, including switching search traffic and later facet traffic. (Vinted Engineering)
  • OkCupid compares Vespa and Elasticsearch for large-scale matching and highlights operational scaling and distribution as a major reason for choosing Vespa. (OkCupid Tech Blog)
  • The Solr vs Vespa guide is specifically written to map Solr concepts to Vespa behavior (ranking defaults, query parameters, request model). (Vespa Blog)

What tends to surprise Solr teams (and directly matches your questions):

  1. “Dynamic ranking” becomes “dynamic inputs + profile selection.” You do not ship new expressions per request. (Stack Overflow)
  2. Filter and group features are fast when modeled as attributes, but you must decide that up front. (VespaDocument)
  3. Sorting does not remove ranking cost unless you switch to unranked. (VespaDocument)

A concrete architecture that fits your “Solr-like agility” requirement

If your core requirement is “we tune ranking a lot, and we do A/B tests constantly,” the most robust Vespa pattern is:

  1. Define a small family of rank profiles (structural variants). (VespaDocument)

  2. Parameterize everything you expect to tune weekly or daily using query inputs (input.query(...)). (VespaDocument)

  3. Do A/B routing outside Vespa (gateway or search service) and set:

    • ranking.profile
    • input.query(...) weights
    • model.defaultIndex fieldset (VespaDocument)
  4. Make filter-only endpoints use unranked so you are not paying for rank-score computation you do not use. (VespaDocument)

  5. Always specify group ordering in grouping expressions if you need deterministic behavior. (VespaDocument)

  6. Use debug tooling sparingly but deliberately (ranking.listFeatures=true) to explain scoring and build training or regression datasets. (VespaDocument)


Summary bullets

  • Rank profiles can live in separate .profile files, but they are deployed config, not hot-loaded runtime files. (VespaDocument)
  • You cannot send ranking expressions in a query. Use profile selection plus query inputs for experiments. (Stack Overflow)
  • Deployments are designed to be online and not disrupt queries and writes. Performance impact depends on what changed. (VespaDocument)
  • Filter-only queries still get a rank score because ranking still runs unless you use unranked. (VespaDocument)
  • Sort by lowest relevance using order by '[relevance]' asc. (VespaDocument)
  • Grouping defaults to -max(relevance()) and can look random on ties. Control it with order(...). (VespaDocument)
  • Solr qf maps to schema fieldsets plus model.defaultIndex and ranking-time boosts. (VespaDocument)