Query API

Use the Vespa Query API to query, rank and organize data. Example:

$ vespa query "select * from music where year > 2001" \
  "ranking=rank_albums" \
  "input.query(user_profile)={{cat:pop}:0.8,{cat:rock}:0.2,{cat:jazz}:0.1}"

Simplified, a query has the following components:

  • Input data
  • Ranking and grouping specification
  • Results
  • Other execution parameters

This guide is an introduction to the more important elements in the API - refer to the Query API reference for details. See query execution below for data flow.

Input

Input data is both structured data and unstructured data:

$ vespa query "select * from music where artist contains \"coldplay\" and userInput(@q)" \
  "q=head"

The first line is the YQL query string, that has both structured input (artist=coldplay) and a reference to unstructured user input. The user input is then given in the second line in the q parameter.

Separating the structured data from the unstructured reliefs the application code from interpreting and sanitizing the input data - it is essentially a blob. Vespa can then use heuristics to deduct the user's intention. User input can also be expressed in the simple query language using the userQuery operator.

Finally, input data can also be ranking query features - here the query feature is called user_profile:

$ vespa query "select * from music where artist contains \"coldplay\" and userInput(@q)" \
  "q=head" \
  "ranking=rank_albums" \
  "input.query(user_profile)={{cat:pop}:0.8,{cat:rock}:0.2,{cat:jazz}:0.1}"

See query execution below.

Input examples

$ vespa query "select * from sources * where userInput(@animal)" \
  "animal=panda"

The userInput() function will access the query property "animal", and parse the property value as a weakAnd query, resulting in:

select * from sources * where weakAnd(default contains "panda")


Changing the value of "animal" without changing the rest of the expression:

$ vespa query "select * from sources * where userInput(@animal)" \
  "animal=panda smokey"

The result is:

select * from sources * where weakAnd(default contains "panda", default contains "smokey")


Combining multiple query properties, and having a more complex expression:

$ vespa query "select * from sources * where range(year, 1963, 2014) and (userInput(@animal) or userInput(@teddy))" \
  "animal=panda" \
  "teddy=bear roosevelt"

The resulting YQL expression is:

select * from sources * where range(year, 1963, 2014) and (weakAnd(default contains "panda") or weakAnd(default contains "bear", default contains "roosevelt"))


Now, consider we do not want the "teddy" field to be treated as its own query segment, it should only be segmented with the linguistic libraries to get recall. Do this by adding a grammar-annotation to the userInput() call:

$ vespa query "select * from sources * where range(year, 1963, 2014) and (userInput(@animal) or ({grammar: "segment"}userInput(@teddy)))" \
  "animal=panda" \
  "teddy=bear roosevelt"

Then, the linguistic library will split on space, and the resulting expression is:

select * from sources * where range(year, 1963, 2014) and (weakAnd(default contains "panda") or default contains phrase("bear", "roosevelt"))

Using a fieldset

Above, the userInput() is run on the default index, as it is not specified. Use a query annotation to use another index:

$ vespa query "select * from sources * where {defaultIndex: 'myindex'}userInput(@q)" \
  q=panda

myindex is here a field or fieldset in the schema. With userQuery(), use model.defaultIndex:

$ vespa query "select * from music where userQuery()" \
  query=panda \
  model.defaultIndex=myindex

Query Profiles

Use a query profile to store query parameters in configuration. This makes query strings shorter, and makes it easy to modify queries by modifying configuration only. Use cases are setting query properties for different markets, parameters that do not change, and so on. Query profiles can be nested, versioned and use inheritance.

Geo Filter and Ranking

Filter by position using latitude and longitude to implement geo search. DistanceToPath is a rank function based on closeness. Using ranking can often improve results instead of geo filtering.

Parameter substitution

Parameter substitution lets you provide query values as request parameters instead of inserting this into the YQL string itself. This simplifies query generation, separating the value of the string/set/array from the YQL string - i.e. the value will not corrupt the YQL string if it contains YQL-like syntax:

  • Simplify query generation, separating the value of the set/array from the YQL string.
  • Speed up query parsing. Using parameter substitution accelerates string parsing.
  • Reduce duplication.

In its simplest form, use userInput() for strings:

... where userInput(@user_input)&user_input=free+text

Lists, maps and arrays can also be used - examples:

# Simple example: provide a set for the IN operator
... where id in (@my_set)&my_set=10,20,30

# Same set, but use the set as a block-list (exclude items in the set)
... where !(id in (@my_set))&my_set=10,20,30

# Use a weightedSet operator
... where weightedSet(field, @my_set)&my_set={a:1,b:2}

It is also great to eliminate data duplication, from Vespa 8.287 one can use parameter substitution with embed:

$ vespa query \
  'yql=select id, from product where {targetHits:10}nearestNeighbor(embedding, query_embedding) or userQuery()' \
  'input.query(query_embedding)=embed(transformer, @query)' \
  'input.query(query_tokens)=embed(tokenizer, @query)' \
  'query=running shoes for kids, white'

Note the use of the parameter named query used by the userQuery() operator. Also note the value substituted in the embed functions.

See the reference for a complete list of formats.

Ranking

Ranking specifies the computation of the query and data. It assigns scores to documents, and returns documents ordered by score. A rank profile is a specification for how to compute a document's score. An application can have multiple rank profiles, to run different computations. Example, a query specifies query categories and a user embedding (from the tensor user guide):

rank-profile product_ranking inherits default {
    inputs {
        query(q_category) tensor<float>(category{})
        query(q_embedding) tensor<float>(x[4])
    }

    function p_sales_score() {
        expression: sum(query(q_category) * attribute(sales_score))
    }

    function p_embedding_score() {
        expression: closeness(field, embedding)
    }

    first-phase {
        expression: p_sales_score() + p_embedding_score()
    }
    match-features: p_sales_score() p_embedding_score()
}
vespa query 'yql=select * from product where {targetHits:1}nearestNeighbor(embedding,q_embedding)' \
  'input.query(q_embedding)=[1,2,3,4]' \
  'input.query(q_category)={"Tablet Keyboard Cases":0.8, "Keyboards":0.3}' \
  'ranking=product_ranking'

Results can be ordered using sorting instead of ranking.

The above rank profile does not do text ranking - there are however such profiles built-in. Text search is described in more detail in Text Matching - find information about normalizing, prefix search and linguistics there.

Grouping

Grouping is a way to group documents in the result set after ranking. Example, return max 3 albums per artist, grouped on year:

$ vespa query "select * from music where true limit 0 | all(group(year) each(max(3) each(output(summary())) ) )"

Fields used in grouping must be attributes. The grouping expression is part of the QYL query string, appended at the end.

Applications can group all documents (select all documents in YQL). Using limit 0 returns grouping results only.

Results

All fields are returned in results by default. To specify a subset of fields, use document summaries. When searching text, having a static abstract of the document in a field, or using a dynamic summary can both improve the visual relevance of the search, and cut bandwidth used.

The default output format is JSON. Write a custom Renderer to generate results in other formats.

Read more on request-response processing - use this to write code to manipulate results.

Query execution

Query execution - from query to response

Phases:

  1. Query processing: Normalizations, rewriting and enriching. Custom logic in search chains
  2. Matching, ranking and grouping/aggregation: This phase dispatches the query to content nodes
  3. Result processing, rendering: Content fetching and snippeting of the top global hits found in the query phase

The above is a simplification - if the query also specifies result grouping, the query phase might involve multiple phases or round-trips between the container and content nodes. See life of a query for a deeper dive into query execution details.

Use trace.explainlevel to analyze the query plan. Use these hints to modify the query plan:

  • Use ranked: false query annotations to speed up evaluation
  • Use capped range search to efficiently implement top-k selection for ranking a subset of the documents in the index.

Query processing and dispatch

  1. A query is sent from a front-end application to a container node using the Query API or in any custom request format handled by a custom request handler, which translates the custom request format to native Vespa APIs.
  2. Query pre-processing, like linguistic processing and query rewriting, is done in built-in and custom search chains - see searcher development.

    The default search chain is vespa - find installed components in this chain by inspecting ApplicationStatus like in the quick-start. Adding &trace.level=4 (or higher) to the query will emit the components invoked in the query, and is useful to analyze ordering.

    This is the integration point to plug in code to enrich a query - example: Look up user profile data from a user ID in the request. Set &trace.level=2 to inspect the search chain components.

  3. The query is sent from the container to the content cluster - see federation for more details. An application can have multiple content clusters - Vespa searches in all by default. Federation controls how to query the clusters, sources names the clusters The illustration above has one content cluster but multiple is fully supported and allows scaling document types differently. E.g. a tweet document type can be indexed in a separate content cluster from a user document type, enabling independent scaling of the two.

    Query processing and dispatch

Matching, ranking, grouping

  1. At this point the query enters one or more content clusters. In a content cluster with grouped distribution, the query is dispatched to one group at a time using a dispatch policy, while with a flat single group content cluster the query is dispatched to all content nodes.
  2. The query arrives at the content nodes which performs matching, ranking and aggregation/grouping over the set of documents in the Ready sub database. By default, Vespa uses DAAT where the matching and first-phase score calculation is interleaved and not two separate, sequential phases. vespa-proton does matching over the ready documents and ranks as specified with the request/schema. Each content node matches and ranks a subset of the total document corpus and returns the hits along with meta information like total hits and sorting and grouping data, if requested.

    Queries
  3. Once the content nodes within the group have replied within the timeout, max-hits / top-k results are returned to the container for query phase result processing. In this phase, the only per hit data available is the internal global document id (gid) and the ranking score. There is also result meta information like coverage and total hit count. Additional hit specific data, like the contents of fields, is not available until the result processing phase has completed the content fetching.

Result processing (fill) phase

  1. When the result from the query phase is available, a custom chained searcher component can process the limited data available from the first search phase before contents of the hits is fetched from the content nodes. The fetching from content nodes is lazy and is not invoked before rendering the response, unless asked for earlier by a custom searcher component.
  2. Only fields in the requested document summaries is fetched from content nodes. The summary request goes directly to the content nodes that produced the result from the query phase.
  3. After the content node requests have completed, the full result set can be processed further by custom components (e.g. doing result deduping, top-k re-ranking), before rendering the response.

HTTP

The Vespa Team does not recommend any specific HTTP client, since we haven't done any systematic evaluation. We have most experience with the Apache HTTP client. See also HTTP best practices (for Vespa Cloud, but most of it is generally applicable). Also see a discussion in #24534.

Use GET or POST. Parameters can either be sent as GET-parameters or posted as JSON, these are equivalent:

$ curl -H "Content-Type: application/json" \
    --data '{"yql" : "select * from sources * where default contains \"coldplay\""}' \
    http://localhost:8080/search/

$ curl http://localhost:8080/search/?yql=select+%2A+from+sources+%2A+where+default+contains+%22coldplay%22

Using POST

The format is based on the Query API reference, and has been converted from the flat dot notation to a nested JSON-structure. The request-method must be POST and the Content-Type must be "application/json", e.g.:

$ curl -X POST -H "Content-Type: application/json" --data '
  {
      "yql": "select * from sources * where true",
      "offset": 5,
      "ranking": {
          "matchPhase": {
              "ascending": true,
              "maxHits": 15
          }
      },
      "presentation" : {
          "bolding": false,
          "format": "json"
      }
  }' \
  http://localhost:8080/search/

Jetty

The Container uses Jetty for HTTP. Configure the http server - e.g. set requestHeaderSize to configure URL length (including headers):

<container version="1.0">
    <http>
        <server port="8080" id="myserver">
            <config name="jdisc.http.connector">
                <requestHeaderSize>32768</requestHeaderSize>
            </config>
        </server>
    </http>
</container>

HTTP keepalive is supported.

Values must be encoded according to standard URL encoding. Thus, space is encoded as +, + as %2b and so on - see RFC 2396.

HTTP status codes are found in the Query API reference. Also see Stack Overflow question.

Timeout

See the reference for how to set the query timeout. Common questions:

  • Does the timeout apply to the whole query or just from when it is sent to the content cluster? If a Searcher goes to sleep in the container for 2*timeout, will the caller still get a response indicating a timeout?

    The timeout applies to the whole query, both container and content node processing. However, the timeout handling is cooperative - if having Searchers that are time-consuming or access external resources, the Searcher code should check Query.getTimeLeft(). So, in this case, you will time out, but only after 2*timeout + some more.

  • During multiphase searching, is the query timeout set for each individual searcher, or is the query timeout set for the entire search chain?

    The timeout is for the entire query (and most Searchers don’t check timeout - use Query.getTimeLeft()). E.g., if a Search Chain has 3 Searchers, it is OK for 1 Searcher to take 497 ms and 2 Searchers to each take 1 ms for a query timeout of 500 ms.

  • If we asynchronously execute several search chains, can we set different query timeouts for each of these chains plus a separate overall timeout for the searcher that performs the asynchronous executions?

    You can set a different timeout in each cloned query you send to any of those chains, and you can specify the timeout when waiting for responses from them.

Error handling

Check for a root: error element in the result:

{
    "root": {
        "errors": [
            {
                "code": 8,
                "summary": "Error in search reply.",
                "source": "music",
                "message": "Could not locate attribute for grouping number 0 : Failed locating attribute vector 'year'. Ignoring this grouping."
            }
        ],

Troubleshooting

If Vespa cannot generate a valid search expression from the query string, it will issue the error message Null query. To troubleshoot, add &trace.level=2 to the request. A missing yql parameter will also emit this error message.

Query tracing

Use query tracing to debug query execution. Enable by using trace.level=1 (or higher). Add trace.timestamps=true for timing info for every searcher invoked. Find a trace example in the result examples below, and try the practical search performance guide.

In custom code, use Query.trace to add trace output.

Result examples

A regular query result:

{
    "root": {
        "id": "toplevel",
        "relevance": 1.0,
        "fields": {
            "totalCount": 1
        },
        "coverage": {
            "coverage": 100,
            "documents": 3,
            "full": true,
            "nodes": 1,
            "results": 1,
            "resultsFull": 1
        },
        "children": [
            {
                "id": "id:mynamespace:music::a-head-full-of-dreams",
                "relevance": 0.16343879032006284,
                "source": "music",
                "fields": {
                    "sddocname": "music",
                    "documentid": "id:mynamespace:music::a-head-full-of-dreams",
                    "artist": "Coldplay",
                    "album": "A Head Full of Dreams",
                    "year": 2015,
                    "category_scores": {
                        "cells": [
                            {
                                "address": {
                                    "cat": "pop"
                                },
                                "value": 1.0
                            },
                            {
                                "address": {
                                    "cat": "rock"
                                },
                                "value": 0.20000000298023224
                            },
                            {
                                "address": {
                                    "cat": "jazz"
                                },
                                "value": 0.0
                            }
                        ]
                    }
                }
            }
        ]
    }
}

An empty result:

{
    "root": {
        "fields": {
            "totalCount": 0
        },
        "id": "toplevel",
        "relevance": 1.0
    }
}

An error result:

{
    "root": {
        "id": "toplevel",
        "relevance": 1.0,
        "fields": {
            "totalCount": 2
        },
        "coverage": {
            "coverage": 100,
            "documents": 4,
            "full": true,
            "nodes": 2,
            "results": 2,
            "resultsFull": 2
        },
        "errors": [
            {
                "code": 8,
                "summary": "Error in search reply.",
                "source": "music",
                "message": "Could not locate attribute for grouping number 0 : Failed locating attribute vector 'year'. Ignoring this grouping."
            }
        ],

A simple search application, many undefined fields. Result for the query /search/?query=blues&hits=3&trace.level=2

{

    "trace": {
        "children": [
            {
                "message": "No query profile is used"
            },
            {
                "message": "Invoking chain 'vespa' [com.yahoo.prelude.statistics.StatisticsSearcher@native -&gt; com.yahoo.prelude.querytransform.PhrasingSearcher@vespa -&gt; ... -&gt; federation@native]"
            },
            {
                "children": [
                    {
                        "message": "Detected language: ENGLISH"
                    },
                    {
                        "message": "Language ENGLISH determined by the characters in the terms."
                    },
                    {
                        "message": "Query parsed to: select * from sources * where default contains \"blues\" limit 3"
                    },
                    {
                        "message": "Child execution",
                        "children": [
                            {
                                "message": "Stemming: [select * from sources * where default contains ({\"origin\": {\"original\": \"blues\", \"offset\": 0, \"length\": 5}, \"stem\": false}\"blue\") limit 3]"
                            },
                            {
                                "message": "Lowercasing: [select * from sources * where default contains ({\"origin\": {\"original\": \"blues\", \"offset\": 0, \"length\": 5}, \"stem\": false, \"normalizeCase\": false}\"blue\") limit 3]"
                            },
                            {
                                "message": "sc0.num0 search to dispatch: query=[blue] timeout=5000ms offset=0 hits=3 grouping=0 : collapse=false restrict=[music]"
                            },
                            {
                                "message": "Current state of query tree: WORD[connectedItem=null connectivity=0.0 creator=ORIG explicitSignificance=false fromSegmented=false index=\"\" isRanked=true origin=\"(0 5)\" segmentIndex=0 significance=0.0 stemmed=true uniqueID=1 usePositionData=true weight=100 words=true]{\n \"blue\"\n}\n"
                            },
                            {
                                "message": "YQL+ representation: select * from sources * where default contains ({\"origin\": {\"original\": \"blues\", \"offset\": 0, \"length\": 5}, \"stem\": false, \"normalizeCase\": false, \"id\": 1}\"blue\") limit 3"
                            },
                            {
                                "message": "sc0.num0 dispatch response: Result (3 of total 10 hits)"
                            },
                            {
                                "message": "sc0.num0 fill to dispatch: query=[blue] timeout=5000ms offset=0 hits=3 grouping=0 : collapse=false restrict=[music] summary=[null]"
                            },
                            {
                                "message": "Current state of query tree: WORD[connectedItem=null connectivity=0.0 creator=ORIG explicitSignificance=false fromSegmented=false index=\"\" isRanked=true origin=\"(0 5)\" segmentIndex=0 significance=0.0 stemmed=true uniqueID=1 usePositionData=true weight=100 words=true]{\n \"blue\"\n}\n"
                            },
                            {
                                "message": "YQL+ representation: select * from sources * where default contains ({\"origin\": {\"original\": \"blues\", \"offset\": 0, \"length\": 5}, \"stem\": false, \"normalizeCase\": false, \"id\": 1}\"blue\") limit 3"
                            }
                        ]
                    },
                    {
                        "message": "Child execution"
                    }
                ]
            }
        ]
    },
    "root": {
        "id": "toplevel",
        "relevance": 1,
        "fields": {
            "totalCount": 10
        },
        "coverage": {
            "coverage": 100,
            "documents": 10,
            "full": true,
            "nodes": 1,
            "results": 1,
            "resultsFull": 1
        },
        "children": [
            {
                "id": "index:0/0/0/dfd9fcfa650b44545ef0b8b2",
                "relevance": "-Infinity",
                "source": "basicsearch",
                "fields": {
                    "sddocname": "music",
                    "title": "Electric Blues",
                    "artist": "",
                    "song": "",
                    "bgndata": "",
                    "sales": "NaN",
                    "pto": -1,
                    "mid": 2,
                    "ew": "blues",
                    "surl": "https://shopping.yahoo.com/shop?d=hab&amp;id=1807865261",
                    "userrate": "NaN",
                    "pid": "",
                    "weight": "NaN",
                    "url": "",
                    "isbn": "",
                    "fmt": "",
                    "albumid": "",
                    "disp_song": "",
                    "pfrom": "NaN",
                    "bgnpfrom": "NaN",
                    "categories": "Blues",
                    "data": "",
                    "numreview": "NaN",
                    "bgnsellers": 0,
                    "image": "",
                    "artistspid": "",
                    "newestedition": "NaN",
                    "bgnpto": "",
                    "year": "NaN",
                    "did": "NaN",
                    "scorekey": "NaN",
                    "cbid": "NaN",
                    "summaryfeatures": "",
                    "documentid": "id:test:music::https://shopping.yahoo.com/shop?d=hab&amp;id=1807865261"
                }
            },
            {
                "id": "index:0/0/0/273d384dc214386c934d793f",
                "relevance": "-Infinity",
                "source": "basicsearch",
                "fields": {
                    "sddocname": "music",
                    "title": "Delta Blues",
                    "artist": "",
                    "song": "",
                    "bgndata": "",
                    "sales": "NaN",
                    "pto": -1,
                    "mid": 2,
                    "ew": "blues",
                    "surl": "https://shopping.yahoo.com/shop?d=hab&amp;id=1804905714",
                    "userrate": "NaN",
                    "pid": "",
                    "weight": "NaN",
                    "url": "",
                    "isbn": "",
                    "fmt": "",
                    "albumid": "",
                    "disp_song": "",
                    "pfrom": "NaN",
                    "bgnpfrom": "NaN",
                    "categories": "Blues",
                    "data": "",
                    "numreview": "NaN",
                    "bgnsellers": 0,
                    "image": "",
                    "artistspid": "",
                    "newestedition": "NaN",
                    "bgnpto": "",
                    "year": "NaN",
                    "did": "NaN",
                    "scorekey": "NaN",
                    "cbid": "NaN",
                    "summaryfeatures": "",
                    "documentid": "id:test:music::https://shopping.yahoo.com/shop?d=hab&amp;id=1804905714"
                }
            },
            {
                "id": "index:0/0/0/b3c74a9bf3aea1e2260311c0",
                "relevance": "-Infinity",
                "source": "basicsearch",
                "fields": {
                    "sddocname": "music",
                    "title": "Chicago Blues",
                    "artist": "",
                    "song": "",
                    "bgndata": "",
                    "sales": "NaN",
                    "pto": -1,
                    "mid": 2,
                    "ew": "blues",
                    "surl": "https://shopping.yahoo.com/shop?d=hab&amp;id=1804905710",
                    "userrate": "NaN",
                    "pid": "",
                    "weight": "NaN",
                    "url": "",
                    "isbn": "",
                    "fmt": "",
                    "albumid": "",
                    "disp_song": "",
                    "pfrom": "NaN",
                    "bgnpfrom": "NaN",
                    "categories": "Blues",
                    "data": "",
                    "numreview": "NaN",
                    "bgnsellers": 0,
                    "image": "",
                    "artistspid": "",
                    "newestedition": "NaN",
                    "bgnpto": "",
                    "year": "NaN",
                    "did": "NaN",
                    "scorekey": "NaN",
                    "cbid": "NaN",
                    "summaryfeatures": "",
                    "documentid": "id:test:music::https://shopping.yahoo.com/shop?d=hab&amp;id=1804905710"
                }
            }
        ]
    }
}

Result for the grouping query /search/?hits=0&yql=select * from sources * where sddocname contains purchase | all(group(customer) each(output(sum(price))))

{

    "trace": {
        "children": [
            {
                "children": [
                    {
                        "message": "Child execution"
                    }
                ]
            }
        ]
    },
    "root": {
        "id": "toplevel",
        "relevance": 1,
        "fields": {
            "totalCount": 20
        },
        "coverage": {
            "coverage": 100,
            "documents": 20,
            "full": true,
            "nodes": 1,
            "results": 1,
            "resultsFull": 1
        },
        "children": [
            {
                "id": "group:root:0",
                "relevance": 1,
                "continuation": {
                    "this": ""
                },
                "children": [
                    {
                        "id": "grouplist:customer",
                        "relevance": 1,
                        "label": "customer",
                        "children": [
                            {
                                "id": "group:string:Jones",
                                "relevance": 9870,
                                "value": "Jones",
                                "fields": {
                                    "sum(price)": 39816
                                }
                            },
                            {
                                "id": "group:string:Brown",
                                "relevance": 8000,
                                "value": "Brown",
                                "fields": {
                                    "sum(price)": 20537
                                }
                            },
                            {
                                "id": "group:string:Smith",
                                "relevance": 6100,
                                "value": "Smith",
                                "fields": {
                                    "sum(price)": 19484
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    }
}