Skip to content

Commit 767ca73

Browse files
Updating error handling for compound retrievers (#115277) (#115427)
Co-authored-by: Elastic Machine <[email protected]>
1 parent ecaf947 commit 767ca73

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import org.apache.lucene.search.ScoreDoc;
1313
import org.apache.lucene.util.SetOnce;
14+
import org.elasticsearch.ElasticsearchStatusException;
15+
import org.elasticsearch.ExceptionsHelper;
1416
import org.elasticsearch.action.ActionListener;
1517
import org.elasticsearch.action.ActionRequestValidationException;
1618
import org.elasticsearch.action.search.MultiSearchRequest;
@@ -21,6 +23,7 @@
2123
import org.elasticsearch.index.query.BoolQueryBuilder;
2224
import org.elasticsearch.index.query.QueryBuilder;
2325
import org.elasticsearch.index.query.QueryRewriteContext;
26+
import org.elasticsearch.rest.RestStatus;
2427
import org.elasticsearch.search.builder.PointInTimeBuilder;
2528
import org.elasticsearch.search.builder.SearchSourceBuilder;
2629
import org.elasticsearch.search.fetch.StoredFieldsContext;
@@ -122,10 +125,17 @@ public final RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOExceptio
122125
public void onResponse(MultiSearchResponse items) {
123126
List<ScoreDoc[]> topDocs = new ArrayList<>();
124127
List<Exception> failures = new ArrayList<>();
128+
// capture the max status code returned by any of the responses
129+
int statusCode = RestStatus.OK.getStatus();
130+
List<String> retrieversWithFailures = new ArrayList<>();
125131
for (int i = 0; i < items.getResponses().length; i++) {
126132
var item = items.getResponses()[i];
127133
if (item.isFailure()) {
128134
failures.add(item.getFailure());
135+
retrieversWithFailures.add(innerRetrievers.get(i).retriever().getName());
136+
if (ExceptionsHelper.status(item.getFailure()).getStatus() > statusCode) {
137+
statusCode = ExceptionsHelper.status(item.getFailure()).getStatus();
138+
}
129139
} else {
130140
assert item.getResponse() != null;
131141
var rankDocs = getRankDocs(item.getResponse());
@@ -134,7 +144,14 @@ public void onResponse(MultiSearchResponse items) {
134144
}
135145
}
136146
if (false == failures.isEmpty()) {
137-
IllegalStateException ex = new IllegalStateException("Search failed - some nested retrievers returned errors.");
147+
assert statusCode != RestStatus.OK.getStatus();
148+
final String errMessage = "["
149+
+ getName()
150+
+ "] search failed - retrievers '"
151+
+ retrieversWithFailures
152+
+ "' returned errors. "
153+
+ "All failures are attached as suppressed exceptions.";
154+
Exception ex = new ElasticsearchStatusException(errMessage, RestStatus.fromCode(statusCode));
138155
failures.forEach(ex::addSuppressed);
139156
listener.onFailure(ex);
140157
} else {

x-pack/plugin/rank-rrf/src/internalClusterTest/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilderIT.java

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
package org.elasticsearch.xpack.rank.rrf;
99

1010
import org.apache.lucene.search.TotalHits;
11+
import org.elasticsearch.ElasticsearchStatusException;
12+
import org.elasticsearch.ExceptionsHelper;
1113
import org.elasticsearch.TransportVersion;
1214
import org.elasticsearch.action.ActionListener;
1315
import org.elasticsearch.action.search.SearchRequestBuilder;
@@ -18,6 +20,7 @@
1820
import org.elasticsearch.index.query.QueryBuilder;
1921
import org.elasticsearch.index.query.QueryBuilders;
2022
import org.elasticsearch.plugins.Plugin;
23+
import org.elasticsearch.rest.RestStatus;
2124
import org.elasticsearch.search.aggregations.AggregationBuilders;
2225
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
2326
import org.elasticsearch.search.builder.SearchSourceBuilder;
@@ -47,7 +50,6 @@
4750
import static org.hamcrest.CoreMatchers.is;
4851
import static org.hamcrest.Matchers.containsString;
4952
import static org.hamcrest.Matchers.equalTo;
50-
import static org.hamcrest.Matchers.greaterThan;
5153
import static org.hamcrest.Matchers.instanceOf;
5254
import static org.hamcrest.Matchers.lessThanOrEqualTo;
5355

@@ -589,11 +591,11 @@ public void testRRFExplainWithAnotherNestedRRF() {
589591
});
590592
}
591593

592-
public void testRRFInnerRetrieverSearchError() {
594+
public void testRRFInnerRetrieverAll4xxSearchErrors() {
593595
final int rankWindowSize = 100;
594596
final int rankConstant = 10;
595597
SearchSourceBuilder source = new SearchSourceBuilder();
596-
// this will throw an error during evaluation
598+
// this will throw a 4xx error during evaluation
597599
StandardRetrieverBuilder standard0 = new StandardRetrieverBuilder(
598600
QueryBuilders.constantScoreQuery(QueryBuilders.rangeQuery(VECTOR_FIELD).gte(10))
599601
);
@@ -615,10 +617,57 @@ public void testRRFInnerRetrieverSearchError() {
615617
)
616618
);
617619
SearchRequestBuilder req = client().prepareSearch(INDEX).setSource(source);
618-
Exception ex = expectThrows(IllegalStateException.class, req::get);
619-
assertThat(ex, instanceOf(IllegalStateException.class));
620-
assertThat(ex.getMessage(), containsString("Search failed - some nested retrievers returned errors"));
621-
assertThat(ex.getSuppressed().length, greaterThan(0));
620+
Exception ex = expectThrows(ElasticsearchStatusException.class, req::get);
621+
assertThat(ex, instanceOf(ElasticsearchStatusException.class));
622+
assertThat(
623+
ex.getMessage(),
624+
containsString(
625+
"[rrf] search failed - retrievers '[standard]' returned errors. All failures are attached as suppressed exceptions."
626+
)
627+
);
628+
assertThat(ExceptionsHelper.status(ex), equalTo(RestStatus.BAD_REQUEST));
629+
assertThat(ex.getSuppressed().length, equalTo(1));
630+
assertThat(ex.getSuppressed()[0].getCause().getCause(), instanceOf(IllegalArgumentException.class));
631+
}
632+
633+
public void testRRFInnerRetrieverMultipleErrorsOne5xx() {
634+
final int rankWindowSize = 100;
635+
final int rankConstant = 10;
636+
SearchSourceBuilder source = new SearchSourceBuilder();
637+
// this will throw a 4xx error during evaluation
638+
StandardRetrieverBuilder standard0 = new StandardRetrieverBuilder(
639+
QueryBuilders.constantScoreQuery(QueryBuilders.rangeQuery(VECTOR_FIELD).gte(10))
640+
);
641+
// this will throw a 5xx error
642+
TestRetrieverBuilder testRetrieverBuilder = new TestRetrieverBuilder("val") {
643+
@Override
644+
public void extractToSearchSourceBuilder(SearchSourceBuilder searchSourceBuilder, boolean compoundUsed) {
645+
searchSourceBuilder.aggregation(AggregationBuilders.avg("some_invalid_param"));
646+
}
647+
};
648+
source.retriever(
649+
new RRFRetrieverBuilder(
650+
Arrays.asList(
651+
new CompoundRetrieverBuilder.RetrieverSource(standard0, null),
652+
new CompoundRetrieverBuilder.RetrieverSource(testRetrieverBuilder, null)
653+
),
654+
rankWindowSize,
655+
rankConstant
656+
)
657+
);
658+
SearchRequestBuilder req = client().prepareSearch(INDEX).setSource(source);
659+
Exception ex = expectThrows(ElasticsearchStatusException.class, req::get);
660+
assertThat(ex, instanceOf(ElasticsearchStatusException.class));
661+
assertThat(
662+
ex.getMessage(),
663+
containsString(
664+
"[rrf] search failed - retrievers '[standard, test]' returned errors. All failures are attached as suppressed exceptions."
665+
)
666+
);
667+
assertThat(ExceptionsHelper.status(ex), equalTo(RestStatus.INTERNAL_SERVER_ERROR));
668+
assertThat(ex.getSuppressed().length, equalTo(2));
669+
assertThat(ex.getSuppressed()[0].getCause().getCause(), instanceOf(IllegalArgumentException.class));
670+
assertThat(ex.getSuppressed()[1].getCause().getCause(), instanceOf(IllegalStateException.class));
622671
}
623672

624673
public void testRRFInnerRetrieverErrorWhenExtractingToSource() {

0 commit comments

Comments
 (0)