Distance feature query #
Recalls every document and scores each by its proximity to an origin — the closer, the higher:
score = boost × pivot / (pivot + distance(document, origin))
A document exactly at the origin scores boost; one pivot units away
scores boost / 2. The decay is gradual — unlike a range filter, a
document three pivots out still scores boost / 4, so nothing is
excluded, only demoted. It answers “rank by recency or by distance”
without a second index or a script.
POST /my-collection/_search
{
"query": {
"distance_feature": {
"field": "production_date",
"pivot": "7d",
"origin": "now"
}
}
}
How it works #
The pivot’s unit decides what distance means for the query: a
duration makes it a time difference (epoch millis, or raw
nanos with a nanos pivot for date_nanos fields), a distance
makes it metres (geo_point fields), and a plain number compares
the field’s numeric values directly. Each document’s stored field
value is read, its distance to the origin computed — absolute
difference for numbers and dates, haversine for geo points — and run
through the formula above; distance is floored at zero, so values
on either side of the origin score identically.
origin resolves lazily: "now" pins at evaluation time (every
segment of one request sees the same instant), an ISO-8601 date
parses to epoch millis, a number passes through, and geo origins
take the [lon, lat] array or the "lat,lon" string. Pivot
durations accept ms, s, m, h, d, w and nanos/ns;
distances accept km, m-less bare numbers (metres), mi, yd,
ft, in, cm, mm and nmi. When a millis-scale pivot meets
nanosecond-scale doc values (values ≥ 10^17), the values are divided
down to millis so the two sides of the subtraction agree — the same
normalization the date_nanos paths apply elsewhere.
The geo form, ranking every document by distance from one point:
POST /places/_search
{
"query": {
"distance_feature": {
"field": "location",
"pivot": "1000km",
"origin": [116.4074, 39.9042]
}
}
}
Parameters for distance_feature
#
field
(Required, string) A numeric,date,date_nanos, orgeo_pointfield.origin
(Required) The anchor:"now", an ISO-8601 date, an epoch number for numeric/date fields;[lon, lat]or"lat,lon"for geo fields.pivot
(Required) The distance at which the score halves: a duration ("1h","7d","100000000nanos"), a distance ("1km","500m","2mi"), or a plain number for numeric fields.boost
(Optional, float, default:1.0).
Use cases #
- Recency re-ranking. As a
bool.shouldclause next to the text query,origin: "now"withpivot: "7d"adds up to one unit of score to fresh log lines and products — recent hits win ties without excluding old ones. - “Nearest first” without geo infrastructure. On a
geo_pointfield, standalone distance_feature orders every place by distance from the shopper’s coordinates; pivot sets how quickly relevance falls off (withinpivot, scores stay above half). - Freshness-ordered feeds. Standalone on a
datefield it is a pure recency sort whose scores are comparable across requests — handy for TTL dashboards and “last seen” views. - Nanos-precision timelines.
date_nanosfields with a"100000000nanos"pivot (100 ms) resolve orderings that collapse at millisecond resolution.
Notes #
The query recalls every document that carries the field — each hit
scores above zero, whether the value is stored as a number, an ISO
date string, or any accepted geo form ([lon, lat] array or
"lat,lon" string). Documents missing the field, or with values
that do not parse for it, are simply not recalled. It pairs naturally
as a bool.should clause (relevance re-ranking) or standalone as a
recency/proximity ordering. Inside nested queries it is not
supported yet (rejected with a named error).
Related #
hybrid—score_functions, the declarative alternative when the decay should combine with other scoring functions.rescore— re-scoring the top window of an already-ranked result, the other place proximity boosts are applied.geo_distance— the filter form: a hard radius that excludes, where distance_feature demotes.sort—_geo_distancesorting when you need distance ordering without score decay.