Skip to content
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

Correct boost in script_score query and error on negative scores #52478

Merged
merged 5 commits into from
Feb 24, 2020

Conversation

mayya-sharipova
Copy link
Contributor

@mayya-sharipova mayya-sharipova commented Feb 18, 2020

Before boost in script_score query was wrongly applied only to the subquery.
This commit makes sure that the boost is applied to the whole score
that comes out of script.

Also provide error 400x error message on negative scores in script_score.

Closes #48465

Before boost in script_score query was wrongly applied only to the subquery.
This commit makes sure that the boost is applied to the whole score
that comes out of script.

Closes elastic#48465
@mayya-sharipova mayya-sharipova added the :Search/Search Search-related issues that do not fall into other categories label Feb 18, 2020
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-search (:Search/Search)

Copy link
Contributor

@matriv matriv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I'm completely inexperienced in the area but I left some comments, especially the one regarding explanation.

@@ -48,9 +48,12 @@ scores be positive or `0`.
--

`min_score`::
(Optional, float) Documents with a <<relevance-scores,relevance score>> lower
(Optional, float) Documents with a score lower
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you remove the reference to the score docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we were referencing relevance scores, while I think the goal of script_score query is to calculate custom scores through some scripts, not a traditional textual relevance score.

@@ -143,7 +148,12 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio
explanation = Explanation.match(score, desc);
}
}

if (boost != 1.0f) {
Copy link
Contributor

@matriv matriv Feb 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be executed also when explanation == null? Or I'm missing something?
Maybe it worths checking for a test that when the boost is != 1 and explanation is false there is no explanation returned regarding the boost?

Also, why not return an explanation if the boost is 1, since it's asked by the user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we handle explanation object is different from other queries, which may be confusing. In script_score query a user can provide his/her custom explanation.

This seems to be executed also when explanation == null?

Not exactly. We start with explanation that is user provided explanation for the script, and if it null, it is substituted by our standard explanation (line 143), so on in line 151 explanation is never null.

Maybe it worths checking for a test that when the boost is != 1 and explanation is false there is no explanation returned regarding the boost?

When is explanation is not asked (explain = false in a search request), we will not even go to this method. This method is executed only when a user requests an explanation.

Also, why not return an explanation if the boost is 1, since it's asked by the user?

I was thinking since boost is an optional parameter, when it is not provided by a user, there is no need to provide an explanation about it.

query: {match_all: {boost: 10}}
script:
source: "doc['i'].value * _score"
boost: 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use a different score here, e.g.: 5 to make it more clear.

@mayya-sharipova
Copy link
Contributor Author

@matriv Thanks for the review. I have tried to address your comments, please continue to review when you have time.

Copy link
Contributor

@matriv matriv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks a lot for responding to my questions and providing some more context!

Copy link
Contributor

@jpountz jpountz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a minor suggestion, otherwise LGTM.

this.explanation = explanation;
}

@Override
public float score() throws IOException {
int docId = docID();
scoreScript.setDocument(docId);
float score = (float) scoreScript.execute(explanation);
float score = (float) scoreScript.execute(explanation) * boost;
if (score == Float.NEGATIVE_INFINITY || Float.isNaN(score)) {
throw new ElasticsearchException(
"script_score query returned an invalid score [" + score + "] for doc [" + docId + "].");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if this error message returned the value produced by the script without the boost.

subs.addAll(Arrays.asList(explanation.getDetails()));
subs.add(Explanation.match(boost, "boost"));
explanation = Explanation.match(explanation.getValue(), explanation.getDescription(), subs);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest wrapping the explanation instead of modifying it in-place: create the scorer with boost=1f a couple lines above, and then here:

if (boost != 1f) {
    explanation = Explanation.match(boost * explanation.getValue().floatValue(), "Boosted score, product of:",
        Explanation.match(boost, "boost"),
        explanation);
}

@mayya-sharipova
Copy link
Contributor Author

@jpountz Thanks for the feedback, I have addressed it.
In the last commit , I have also corrected the exception to make sure a 400 error is returned when a score is negative.

@mayya-sharipova mayya-sharipova merged commit 556ee9a into elastic:master Feb 24, 2020
@mayya-sharipova mayya-sharipova deleted the script_score_boost branch February 24, 2020 15:46
mayya-sharipova added a commit to mayya-sharipova/elasticsearch that referenced this pull request Feb 24, 2020
Before boost in script_score query was wrongly applied only to the subquery.
This commit makes sure that the boost is applied to the whole score
that comes out of script.

Closes elastic#48465
mayya-sharipova added a commit that referenced this pull request Feb 24, 2020
Before boost in script_score query was wrongly applied only to the subquery.
This commit makes sure that the boost is applied to the whole score
that comes out of script.

Closes #48465
if (score == Float.NEGATIVE_INFINITY || Float.isNaN(score)) {
throw new ElasticsearchException(
"script_score query returned an invalid score [" + score + "] for doc [" + docId + "].");
if (score < 0f || Float.isNaN(score)) {
Copy link
Contributor

@jtibshirani jtibshirani Mar 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mayya-sharipova it looks like this PR also fixed a bug in script_score queries where we allowed negative scores. I think we should add a note to the breaking changes docs and also update the PR description to make it clear we included this change.

Copy link
Contributor Author

@mayya-sharipova mayya-sharipova Mar 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jtibshirani Thanks. Even before without this change a user would get an error if their script_score query produced a negative score. They would just get it from a different place, one of them from the Lucene here

So the only thing changed from a user perspective is an error message and error status code (before was 500, not 400x). Do you think it warrants a breaking change notice?
+1 for include this in the PR description

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this out using an Elasticsearch 7.6 build, and didn't receive an error:

PUT my_index/_doc/1?refresh
{
  "field": "value"
}

GET my_index/_search
{
  "query": {
    "script_score": {
      "query": {
        "match_all": {}
      },
      "script": {
        "source": "-1000"
      }
    }
  }
}

The line you linked to is an assert, so perhaps these Lucene checks didn't always catch the issue in non-test environments.

Copy link
Contributor Author

@mayya-sharipova mayya-sharipova Mar 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jtibshirani Thanks for uncovering this. I understood what happened:

  • Before 7.5, script_score query was using ScriptScoreFunction that was returning 400 error with a negative score.
  • From 7.5, we have changed it to not use ScriptScoreFunction but forgot to add a condition for a negative score. But TopScoreDocCollector assertion is tripped, causing fatal error in the dev mode. But I guess we silence these assertions in a production mode as we don't see any visible errors or error log messages.

So, negative scores were wrongly allowed only in 7.5-7.6 versions, so to me it doesn't look like a really breaking change. But I think it is still worth to add a note with explanation in release notes. I will do that. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an explanation to the release notes makes sense to me. I agree it shouldn't be presented as a typical 'breaking change', it is more like a regression that we fixed. Perhaps we could add a unit test along with the release notes update, to prevent a future regression?

@mayya-sharipova mayya-sharipova changed the title Correct boost calculation in script_score query Correct boost in script_score query and error on negative scores Mar 3, 2020
mayya-sharipova added a commit to mayya-sharipova/elasticsearch that referenced this pull request Mar 4, 2020
7.5 and 7.6 had a regression that allowed for
script_score queries to have negative scores.
We have corrected this regression in elastic#52478.
This is an addition to elastic#52478 that adds
a test and release notes.
mayya-sharipova added a commit that referenced this pull request Mar 5, 2020
7.5 and 7.6 had a regression that allowed for
script_score queries to have negative scores.
We have corrected this regression in #52478.
This is an addition to #52478 that adds
a test and release notes.
mayya-sharipova added a commit that referenced this pull request Mar 5, 2020
7.5 and 7.6 had a regression that allowed for
script_score queries to have negative scores.
We have corrected this regression in #52478.
This is an addition to #52478 that adds a test for this.

Related to #53133
@jakelandis jakelandis removed the v8.0.0 label Jul 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>bug :Search/Search Search-related issues that do not fall into other categories v7.7.0 v8.0.0-alpha1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Apply boost in script_score query to the whole query
6 participants