-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replacing the function_score with discrete queries #27588
Comments
I think this is just moving the complication from one form to another. The fact that there is a "query" to control the score of other queries is what is confusing. Function score is about manipulating the score. IMO we should use scripts for any manipulation of the score. Imagine every query node allowing a script to modify the score of that node. In the case of boolean query, we can provide an array of sub scores. Things like decay should just be functions made available in painless. |
@rjernst could you provide some examples of what this would look like? |
I had actually understood the proposed decay queries as not modifying the score of an inner query but as both selecting and scoring the documents. So for example with the |
Sorry, I probably didn't explain well in the OP. ++ What @colings86 said, my understanding is that The rest -- min/max, cutoff, etc -- I think are definitely up for debate as to their usefulness and how they should be implemented. FWIW, I don't think queries controlling other query scores is unusual/unprecedented. I'm not opposed to widespread usage of scripting, but I think we should be careful not to push too much complexity into scripting. Scripts are troublesome to debug and generally more difficult for ORM/high-level clients/applications to integrate (imo). For example, an app can generate a query and hand it over to another portion of code, which blindly wraps it in a |
One thing that could be useful in
|
I want to second normalizing a score! I typically use boolean queries with multiple match queries as the base query which can yield top scores in the 10s or 100s or sometimes less than 1.0. It is very difficult to combine popularity or recency because they aren't necessarily on the same scale. Additionally the base query's scale can change between queries adding another level of complexity to manually setting boosts/weights. Being able to normalize the base query and then combine with a normalized |
#15670 has another idea in the topic of normalizing scores: add bbility to set percentage influence of each function in function score query |
Pinging @elastic/es-search-aggs |
@rpedela and others interested in normalization: It turned out that correctly implemented normalization will be quite slow, as we first need to iterate across all docs to find min and max values across all docs for this feature/score, and then again to iterate across docs to assign them score as As an alternative, we have added a new query type - feature query, that allows to boost the score of documents based on the values of numeric features (e.g. popularity, recency etc). This query has an additional benefit of skipping non-competitive hits, and being very fast in certain cases. |
@mayya-sharipova Is there a schedule regarding replacing function_score with smaller/more specialised queries? I'm still interested in a solution for #24910 (and would be happy to try and contribute if that's the way to go), but also would be interested in expanding the current behaviour for field_value_factor to support at least another option: using the value specified as factor as either a multiplication or a sum. Would any of this be welcome, as either changes to current function_score, or as something new? |
@lmenezes I have marked #24910 for our next team discussion, and I will update the issue with what we have decided. About adding another option for |
@mayya-sharipova For performance. We use function score a lot, normally combining multiple functions on the same query over datasets of varying size(ranging from few million to >1B). We have come to find that the performance difference between field_value_factor and script_score can be pretty big. For one of our use cases, replacing a single field_value_factor function with a script_score is around 25% slower on the script side. Replacing 2 field_value_factor with equivalent script_scores, this already goes up to 40%(although combining the logic for both script_score on the same function keeps the difference at around 25%, so this is good). All of this on a 20mm dataset, with queries that will match <5million documents. I can reproduce these numbers by running with JMeter a coule thousand queries where the functions look like this(query/filters omitted for simplicity), where only the functions vary: "functions": [
{
"script_score": {
"script": {
"params": {
"foo": 10
},
"source": "Math.log(params.foo * doc['some_field'].value)"
}
}
}
] "functions": [
{
"field_value_factor": {
"field": "some_field",
"factor": 10,
"modifier": "log2p"
}
}
] & "functions": [
{
"script_score": {
"script": {
"params": {
"foo": 10,
"bar": 50
},
"source": "Math.log(params.foo * doc['some_field'].value) + Math.log(params.bar * doc['some_other_field'].value)"
}
}
}
] "functions": [
{
"field_value_factor": {
"field": "some_field",
"factor": 10,
"modifier": "log2p"
}
},
{
"field_value_factor": {
"field": "some_other_field",
"factor": 50,
"modifier": "log2p"
}
}
] So, I do understand that script_score opens doors to more sophisticated use cases, and that it really isn't realistic to model every single scenario with a special function. But I'm still hoping the existing functions won't be dropped(or that script_score performance will be close enough) :) |
@lmenezes Than you for the thorough description of your use-case. We will definitely consider in making our final decision about the future of function score query. |
@lmenezes Can you try using |
@rjernst Just ran a test including expression scripts to see how this played out. Results are as following: Script(painless) "functions": [
{
"script_score": {
"script": {
"params": {
"constant": 2
},
"source": "Math.log((params.constant * doc['some_field'].value) + 1)"
}
}
}
]
Script(expression) "functions": [
{
"script_score": {
"script": {
"lang": "expression",
"source": "ln((2 * doc['some_field'].value) + 1)"
}
}
}
]
field_value_factor "functions": [
{
"field_value_factor": {
"field": "some_field",
"factor": 2,
"modifier": "ln1p"
}
}
]
So, there is some improvement when comparing painless with expression, but not really significant. Field value factor still offers much better performance. |
@lmenezes Thanks for presenting an example. But from your example, the difference between |
@mayya-sharipova A 20% difference doesn't seem so little to me(specially when this is a single function of potentially many). Looking forward to the results of your performance tests and any other info regarding the future of function score query in general :) |
+1 for sigmoid function. |
This is becoming very crucial for our work especially Scripted Boolean and Band Pass queries. What we are dealing is a use case where our scoring signals are coming from individual child queries, and they have to be reduced using a certain combination. with a possibility of setting a min-max bandpass filter . Since there is no way I can pass the output of one query as the input of the other except in rescoring (even in rescoring, I'm only able to to some predefined actions like addition, linear combination of scores, not a script), this limits our ability to retrieve good documents from a pool. Implementing such functionality like this would immensely help us. In machine learning based retrievals (especially models tuned with learning to rank algorithms), we have some derived variables which should be dynamically computed at the query time. As an example, if one of my parameters of my model is the amount of rain in the last month for a particular city, I would like to do a full text search on the city and compute last month rain using an aggregation. This input needs to be passed on to a final "reduce" query where I will specify how that should be collapsed into a single score. The final model could be a linear combination of such scores. Coming to the band pass filter, If the amount of rain is too low or too high, I might want to consider that as an edge case and score it less because my model would do wrong prediction in such cases. @mayya-sharipova I see that you are working on reimplementation of the old function_score query. It would be great if you could consider these features. Please let me know if there is anything I can help with. Thanks! |
@Sandeep42 Thank you very much for your input. For our new script_score query, we don't plan to do anything fancy, it will be just an imitation of function_score query with some extra functions added. For your use case to combine signals from individual child queries, you can:
|
@mayya-sharipova Thanks for quick response. I will try to understand custom rescorer. |
We have met and discussed whether we should implement the proposed on this issue queries. We have not reached a conclusion yet, and need another follow-up meeting. For now, we still can't deprecate |
We have met again, and discussed the status of
{
"query": {
"script_score": {
"queries": [
"match": {
"message": "elasticsearch"
},
"match": {
"author": "shay banon"
}
],
"script": {
"source": "_scores[0] + _scores[1]"
}
}
}
}
{
"query": {
"script_bool": {
"queries": [
"match": {
"message": "elasticsearch"
},
"match": {
"author": "shay banon"
}
],
"script": {
"source": "_scores[0] + _scores[1]"
}
}
}
} Points that still need to be clarified:
|
Proposals 1 and 2 look very similar, only the name of the query is changing?
I'm afraid this might make the API harder to use compared to 0 as you would likely need to check every input score against -1 eg. if you want to sum up scores?
If something like in-order is a popular ask, we might want to implement it as a proper query instead, which could then leverage upcoming optimizations to skip documents whose score is not competitive. These things are harder (impossible?) to do with scripted scores.
Let's not expose this in scripts, this API is not properly implemented across all queries. |
Is there any update on this topic? :) This (#24910) is currently still painful and solved by work arounds on our side. I understand you want to avoid changes on function score in favor of these discrete queries, but so far this is not yet available and the expected behaviour on function score is difficult to work with. Scripting would be an option, but performance wise it is limiting. |
Seriously, removing function score for scripts? First from my experience it seems to have quite a bit better performance than script_score (at least script_score wrapped in function score). Next, I find scripts quite annoying, as they mean you need to learn and handle another language and API basically. I also like that function score and queries have to basically be declarative, while scripts you can write in some abhorrent imperative style :) . Generally the title of this issue is very appealing to me though. Having light wrapper queries operating on scores of sub-queries would be neater. While replacement of function score with script score seems like step back in my eyes. |
@morphles Thank you for voicing your concerns. I agree performance is indeed a valid issue in scripts and we are working on addressing it. All other matters that you voiced are of personal preferences. |
Well another language to learn might be mildly personal, but also has some objectivity to it. Why use more parts than needed? Why learn/use more stuff if you can do it with less? Granted in web dev world this "insanity" (in my eyes) has no limit, so one must get used to it by necessity, still... I'd rather not need billions of tools :) |
The DSL is a language, and unique within Elasticsearch, so it's already something one must learn. While declarative DSLs like the json used for queries are nice for certain situations, in the case of scoring and many other advanced cases, where basic arithmetic and mathematical functions are desired, having a procedural language is much more natural than eg declaring a "sum" node with children and various keyed settings. It is also much more straightforward to reuse this procedural pattern in advanced cases throughout Elasticsearch, and this is why we are continuing to expand the places scripting can be used. Additionally, the scripting and painless infrastructures are highly extendable to add additional functions, while function score was minimally extendable and extremely cumbersome to do so. We appreciate the feedback, especially the note about performance and as Mayya noted we are working on improving that. |
Well DSL or not, script score is still more stuff DSL + painless, which is elastic specific thing. So while I agree mostly with what you wrote, I'm still for keeping function_score :) . What in my eyes would be really nice, is extending DLS to allow to operate on "named predicates". For example in my function_score I have lots of filters with names, in case I need to return them what matched. So what would be convenient would be to be able to list predicates (as in basically sub queries of various sort) with names, and then have expression on those names. |
Could you elaborate on what "importing" means here?
By this you mean you are tagging your filters with |
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html
Granted this means the expression lang and available functions need to be defined, which might be close to scripting. Still, I think such would allow comparatively super easy and succinct use of fairly customizable and flexible scoring (and would solve one nasty issue with function_score - if you need score value of some query in multiple places, well, it ain't gona be all that fun with function score, while with this just repeat name; ofc same is available in script :) ). |
A note that I added short analysis of |
We had a plan to deprecate function_score query with script_score query, but ran into a roadblock of missing functionality to combine scores from different functions (particularly "first" script_score). Wee have several proposal to address this missing functionality: [scripted_boolean](elastic#27588 (comment)) [compound_query](elastic#51967) [first_query](elastic#52482) But for now, we decided not to deprecate function_score query, and hence we need to remove any mention that we are deprecating it. Relates to elastic#42811 Closes elastic#71934
Closing this issue, as we have an alternative proposal for |
We had a plan to deprecate function_score query with script_score query, but ran into a roadblock of missing functionality to combine scores from different functions (particularly "first" script_score). Wee have several proposal to address this missing functionality: [scripted_boolean](#27588 (comment)) [compound_query](#51967) [first_query](#52482) But for now, we decided not to deprecate function_score query, and hence we need to remove any mention that we are deprecating it. Relates to #42811 Closes #71934
We had a plan to deprecate function_score query with script_score query, but ran into a roadblock of missing functionality to combine scores from different functions (particularly "first" script_score). Wee have several proposal to address this missing functionality: [scripted_boolean](#27588 (comment)) [compound_query](#51967) [first_query](#52482) But for now, we decided not to deprecate function_score query, and hence we need to remove any mention that we are deprecating it. Relates to #42811 Closes #71934
We had a plan to deprecate function_score query with script_score query, but ran into a roadblock of missing functionality to combine scores from different functions (particularly "first" script_score). Wee have several proposal to address this missing functionality: [scripted_boolean](#27588 (comment)) [compound_query](#51967) [first_query](#52482) But for now, we decided not to deprecate function_score query, and hence we need to remove any mention that we are deprecating it. Relates to #42811 Closes #71934
Overview
The
function_score
is a powerful query, but can be somewhat unwieldy and difficult to use. It is a monolithic query that has many parameters and options which makes it difficult for new users to learn. It can be difficult to tweak, and now that we've moved to BM25, the defaults don't work well (namely, multiplying scores).We'd like to deprecate
function_score
and replace it's functionality with a number of discrete queries. The guiding principle is that these replacement queries will be small, simple and single-purpose. Individual queries should be simpler to implement and maintain, easier for users to learn and should allow the same functionality asfunction_score
by mixing/matching as required.New queries / needed functionality
Arbitrary numeric functions
This query will allow working with arbitrary numerics and allow applying various mathematical functions. For example, a document may have a
popularity
field that you wish to roll into the score somehow.Functions should include those of
field_value_factor
, like logarithm, sqrt, reciprocal, etc. We'd also like to include a sigmoid and rational function. The query should also be able to return just the value itself. I think the random number functionality offunction_score
can be rolled into this too.We could potentially implement this query entirely through scripting, assuming we can provide custom functions through the script context for more complicated operations like sigmoid.
Distance: Numeric and Time
This query would provide a set of decay functions that allow you to score the "distance" from a field value to some point. When dealing with dates this "distance" would essentially represent recency, while with numerics it'd just be a geometric distance.
We'll likely want numeric/time to be grouped together since the operations are essentially the same.
Distance: Geo
This would be similar to numeric/time distance, except operating on geo points and physical distance. The thinking is that geo would be separate, as we may have plans in the future for geo querying to become more robust in general. Geo points are also sufficiently different that the syntax will likely need to be a bit different.
Otherwise the functionality is similar, providing a set of decay functions.
Potential queries
Some other potential queries that we kicked around, unsure if they are needed or quite how the functionality would work. Some of these have a direct comparison in function_score, some are only tangentially related.
Min/Max query
Function score allows taking the min or max score from the set of functions. We could potentially add a combination query that executes the children, then only passes on the min/max score from the children.
Bandpass query / cutoff score query
Function score allows setting max thresholds on scores generated by the functions. It may be useful to have a query that allows setting min or max or both, and the scores that come out of the query would be limited to those values. Similar to a constant score in that it wraps a set of queries, but instead of setting a single score it just limits the scores that are generated to the range.
Note this is different from the above min/max query, in that it limits the produced score to a range, whereas min/max simply takes the min or max score as-is.
Would allow us to remove
min_score
(#13115), and limit individual queries (#17348)In-order Boolean
One unique aspect of
function_score
is the ability to use only the "first" matching function. There's no other place in the query DSL that allows "short-circuiting" evaluation... the equivalent "first" functionality would require a complex set of must/must_not boolean conditions.So it may be useful to implement an "in-order" boolean query which evaluates child queries in their order and allows exiting after some criteria is met (
first
, etc).It's not entirely clear how useful this functionality is outside of the
function_score
though, and we'd be interested to hear use-cases for thisfirst
behavior.Scripted Boolean
Related to all the above, it may be useful to have a boolean that executes all the child queries and provides those scores to a script, which would then decide how to combine the scores. This could allow very sophisticated behavior by allowing the user to script away which scores are included (e.g. if they meet a criteria, or only if the total boolean exceeds a threshold, etc) and how they are combined
The downside is that all child queries must be run so that all scores can be collected. And the syntax/script interface would likely be complex
Related issues
#23850
#15670 (by boosting each individual decay query)
/cc @mayya-sharipova @colings86 @clintongormley did I miss anything?
The text was updated successfully, but these errors were encountered: