Repository navigation
Conversation
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open findings
What changed in this PR
This PR makes Elasticsearch/OpenSearch reads fail when responses contain partial shard results (or time out), unless a new index.[X].elasticsearch.allow-partial-results option is enabled, preventing silent truncation in searches/scroll/PIT paging, counts, and aggregations.
Changes:
- Add shard/timed-out parsing (
_shards,timed_out) and enforce completeness checks across search/scroll/PIT/count/agg responses. - Send
allow_partial_search_results=falseon supported search-API requests (ES 7+ / OpenSearch) and route counts through_searchto support that behavior. - Harden paging/caching behavior after page failures (scroll/PIT contexts released; SubqueryIterator avoids probing/caching after failures) and add extensive test coverage + docs.
| File | Description |
|---|---|
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestElasticSearchClient.java | Adds allow_partial_search_results parameter injection and enforces shard completeness/time-out checks for reads. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestShardStatistics.java | New model for _shards parsing and failure description formatting. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestSearchResponse.java | Parses _shards and timed_out for search-like responses. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestCountResponse.java | Updates count parsing to read totals from search-style hits.total and shard/timed-out info. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestAggResponse.java | Adds shard and timed_out parsing for aggregation responses. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestClientSetup.java | Wires new allow-partial-results option into the REST client. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/TransientFailures.java | Detects search timeouts from response bodies to avoid retrying them. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchScroll.java | Ensures failed scroll pages release the scroll context and rethrow consistently. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchPointInTime.java | Ensures failed PIT pages close PIT and rethrow consistently. |
| janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchIndex.java | Introduces config option and updates retry classification to exclude search timeouts. |
| janusgraph-core/src/main/java/org/janusgraph/graphdb/util/SubqueryIterator.java | Prevents probing/caching after index read failure to avoid masking the original exception. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/rest/RestClientShardFailureTest.java | New unit tests validating shard failure/time-out handling and parameter sending behavior. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/rest/RestClientCountAndAggregationTest.java | Updates tests for count-via-search behavior and ES6 vs ES7+ total parsing. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/rest/RestClientRetryTest.java | Adds test ensuring search timeouts aren’t retried even if status is in retry codes. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/ElasticSearchExceptionConversionTest.java | Adds timeout-body coverage for permanent vs temporary exception conversion. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/ElasticSearchFailedPageCacheTest.java | New tests ensuring failed paging results are not cached in transaction subquery cache. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/ElasticsearchIndexTest.java | Integration tests for shard failures and allow-partial-results behavior on real clusters. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/ElasticSearchScrollTest.java | Tests that failed later scroll pages release context and rethrow on subsequent calls. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/ElasticSearchPointInTimeTest.java | Tests that failed later PIT pages close PIT and rethrow on subsequent calls. |
| janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/rest/RestClientSetupTest.java | Verifies new config option is mapped into the client with correct default. |
| docs/index-backend/elasticsearch.md | Documents new failure semantics and allow-partial-results option. |
| docs/configs/janusgraph-cfg.md | Adds configuration reference entry for allow-partial-results and updates related option text. |
| docs/changelog.md | Adds changelog section describing behavioral change and new option. |
🧠 Review effort: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
2 open findings
3 resolved since last review
🧠 Review effort: Lite
Fixes JanusGraph#5095 Elasticsearch answers a search, a page of a scroll or of a point in time, a count or an aggregation which some shards fail with the results of the others, and fails it only when every shard failed. A shard fails where, for example, its search context expired on its node but not on the others, it isn't available, or it rejects the request, and a shard which runs out of a search's time answers with the hits it found until then. JanusGraph read _shards.total but never _shards.failed, so it took such an answer as complete: ElasticSearchScroll and ElasticSearchPointInTime took a page with fewer hits than its size for the last one, which ended the result early, and a count or an aggregation left the failed shards out, all without an error. RestElasticSearchClient now fails every answer whose _shards report shards that didn't answer, failed ones or ones counted neither as successful nor as failed, which is how OpenSearch reports a shard that isn't available, and every answer which ran out of time. The IOException names the index, shard, type and reason of each failure listed. The client also sends allow_partial_search_results=false with every request to the search API (a search, the first page of a scroll, a page of a point in time, a count, an aggregation) to Elasticsearch 7 and later and to OpenSearch, which then fail it themselves with a status that retry-error-codes classifies, such as 503 for a shard which isn't available. A count is now a search for no hits which counts them all, which is what the count API runs, as the count API rejects that parameter; so do the later pages of a scroll, which only the check covers. JanusGraph doesn't reattempt a search which ran out of time, whatever its status, 429 on Elasticsearch 9.5.4 and 504 on 8.15.3: TransientFailures recognises search_timeout_exception, and query_phase_execution_exception "Time exceeded" of Elasticsearch 7 and OpenSearch, for RestElasticSearchClient and ElasticSearchIndex alike. The low-level RestClient still sends a request answered with 502, 503 or 504 to its next host. ElasticSearchScroll releases its context when a page fails, as ElasticSearchPointInTime closes its point in time, and both rethrow the failure of a page on every later call rather than answer that the result ended. SubqueryIterator, closed after reading the index failed, as closing a traversal does, no longer asks the index whether the result ended. That could fail again, and throw that failure from the close, wrapped, in place of the read's, skipping the closes after it. Nor does it cache the ids read before the failure as the whole result, as it could after a page of a point in time failed. The new MASKABLE option index.[X].elasticsearch.allow-partial-results, false by default, uses the results of the shards which answered instead, and the hits found before a search ran out of time, as before. Signed-off-by: Oleksandr Porunov <alexandr.porunov@gmail.com> Co-authored-by: Oleksandr Porunov <alexandr.porunov@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
425869e to
cf0bf44
Compare
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
4 open findings
EntityUtils.toString(entity)consumes the response body stream for non-repeatable entities. Since… · New The completeness check will incorrectly fail valid responses that haveskipped > 0(common in… · New The completeness check will incorrectly fail valid responses that haveskipped > 0(common in… · New This changes count requests toGET /_searchwith a request body. While Elasticsearch accepts… · New
2 resolved since last review
🧠 Review effort: Lite
| public static boolean isSearchTimeout(Throwable failure) { | ||
| if (!(failure instanceof ResponseException)) { | ||
| return false; | ||
| } | ||
| final Response response = ((ResponseException) failure).getResponse(); | ||
| final HttpEntity entity = response == null ? null : response.getEntity(); | ||
| if (entity == null) { | ||
| return false; | ||
| } | ||
| try { | ||
| return mentionsTimeout(MAPPER.readValue(EntityUtils.toString(entity), Object.class)); | ||
| } catch (IOException | RuntimeException notJson) { | ||
| return false; | ||
| } | ||
| } |
| private int total; | ||
|
|
||
| //Shards which were skipped because they can't match are counted here too | ||
| private int successful; | ||
|
|
||
| private int failed; | ||
|
|
||
| //One for each index, type and reason of failure, as Elasticsearch groups them. OpenSearch counts a shard which | ||
| //wasn't available neither among the successful nor among the failed shards, and lists no failure for it | ||
| private List<Map<String, Object>> failures; |
| public boolean isComplete() { | ||
| return failed == 0 && successful >= total; | ||
| } |
| final Request request = new Request(REQUEST_TYPE_GET, REQUEST_SEPARATOR + indexName + REQUEST_SEPARATOR + "_count"); | ||
| //A search for no hits which counts them all, which is what the count API runs. The search API can be told not | ||
| //to allow partial results, which the count API rejects | ||
| final Request request = searchRequest(REQUEST_TYPE_GET, |
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
6 open issues
EntityUtils.toString(entity) consumes the response body. Since isSearchTimeout(...) is called… · New The completeness check will incorrectly fail valid responses that haveskipped > 0(common in… The completeness check will incorrectly fail valid responses that haveskipped > 0(common in…EntityUtils.toString(entity)consumes the response body stream for non-repeatable entities. Since… This change sends a request body to the_searchendpoint using HTTP GET. While Elasticsearch… · New This changes count requests toGET /_searchwith a request body. While Elasticsearch accepts…
Review effort: Lite
| public static boolean isSearchTimeout(Throwable failure) { | ||
| if (!(failure instanceof ResponseException)) { | ||
| return false; | ||
| } | ||
| final Response response = ((ResponseException) failure).getResponse(); | ||
| final HttpEntity entity = response == null ? null : response.getEntity(); | ||
| if (entity == null) { | ||
| return false; | ||
| } | ||
| try { | ||
| return mentionsTimeout(MAPPER.readValue(EntityUtils.toString(entity), Object.class)); | ||
| } catch (IOException | RuntimeException notJson) { | ||
| return false; | ||
| } | ||
| } |
| final Request request = new Request(REQUEST_TYPE_GET, REQUEST_SEPARATOR + indexName + REQUEST_SEPARATOR + "_count"); | ||
| //A search for no hits which counts them all, which is what the count API runs. The search API can be told not | ||
| //to allow partial results, which the count API rejects | ||
| final Request request = searchRequest(REQUEST_TYPE_GET, |


Fixes #5095
Elasticsearch answers a search, a page of a scroll or of a point in time, a count or an aggregation which some shards fail with the results of the shards which answered, with status 200, and fails it only when every shard failed. A shard fails, for example, where its search context expired on its node but not on the others, where it isn't available, or where it rejects the request. A shard which runs out of a search's time answers with the hits it found until then. JanusGraph read
_shards.totalbut never_shards.failed, so it took such an answer as complete.ElasticSearchScrollandElasticSearchPointInTimetook a page with fewer hits than its size for the last one, which ended the result early, and a count or an aggregation left the failed shards out, all without an error. Such a read now fails, with the shard failures in its error, unless the new optionindex.[X].elasticsearch.allow-partial-resultsallows partial results.Changes
RestElasticSearchClient:IOException, where:failedabove 0);successfulbelowtotal), which is how OpenSearch 2.19.6 and 3.9.0 report a shard which isn't available, without listing a failure for it. Elasticsearch 7.17.8 and 9.5.4 count such a shard as failed. Shards which a search skips because they can't match count as successful;timed_out), where Elasticsearch wasn't told not to allow partial results._shards.failures: its index and shard where the answer tells them, its type and reason, and the type and reason of its cause. For example:Elasticsearch answered a page of a scroll with the results of only 1 of 2 shards: search_context_missing_exception: No search context found for id [26]. Set index.[X].elasticsearch.allow-partial-results to true to use such results.allow_partial_search_results=falsewith every request to the search API: a search, the first page of a scroll, a page of a point in time, a count and an aggregation. It goes to Elasticsearch 7 and later and to OpenSearch, whose API is that of 7, and they then fail such a request themselves. Releases of Elasticsearch 6 before 6.3 reject the parameter, and the major version doesn't tell them apart, so Elasticsearch 6 only gets its answers checked.size0,track_total_hitstrue,terminate_afterwhere the count is bounded), which is what the count API runs. The count API rejects the parameter, and its answer doesn't tell that a shard ran out of time./_search/scroll) don't take the parameter, so only the check covers them.scrollparameter of the first page of a scroll is now added to the request rather than written into its path; the request is the same.TransientFailures.isSearchTimeout: JanusGraph doesn't reattempt a search which ran out of time, whatever its status, inRestElasticSearchClientor throughBackendOperation.search_timeout_exceptionand status 429, and 8.15.3 with 504, both in the defaultretry-error-codes. Elasticsearch 7.17.8 and 6.6.0 and OpenSearch 2.19.6 and 3.9.0 fail it withquery_phase_execution_exception"Time exceeded" and 500.BackendOperationfor up tostorage.read-time.RestClientstill sends a request answered with 502, 503 or 504 to its next host, where several are configured, and takes the host for dead for a while (isRetryStatus). So a search which ran out of time on Elasticsearch 8.15.3 runs once on each host which the client doesn't take for dead. It decides that from the status before JanusGraph can read the body. Its only lever for a single request is theignoreparameter, which would also stop it from moving on from a gateway's 504 for a dead node, so this is left as it is.RestShardStatistics(new): the_shardssection of an answer, and the description of its failures.RestSearchResponseparses_shardsinto it, where it read onlytotalinto a map.RestCountResponse, which now reads the answer of a search, andRestAggResponseparse it too, and all three readtimed_out.ElasticSearchScrollreleases its scroll context when a page fails, with an I/O error or at runtime, asElasticSearchPointInTimealready closes its point in time. Before, a page which only some shards answered came back short, which ended the scroll and released its context.SubqueryIterator(janusgraph-core) remembers that reading the index failed. When it is closed afterwards, as TinkerPop'sfill(),iterate()andforEachRemaining()close the traversal in afinally, it neither asks the index whether the result ended nor caches the ids it read.close()then threw that failure, whichCloseableIterator.closeIteratorwraps inRuntimeException, in place of the read's. On master, that happened after a failed page of a scroll, whichhasNext()requested again. The closes after it in the chain, such as the second index's inResultMergeSortIterator,MultiIteratororMultiDistinctOrderedIterator, were skipped, and their scroll or point in time stayed open until its keep-alive.ElasticSearchPointInTimeanswered on master after a failed page. The transaction's cache of index results then kept the ids read before the failure as the whole result of the same query for the rest of the transaction.ElasticSearchScrollandElasticSearchPointInTimerethrow the failure of a page on every later call, rather than answer that the result ended, for any other consumer of their streams.index.[X].elasticsearch.allow-partial-results(Boolean, defaultfalse, MASKABLE). Set totrue, the client neither sends the parameter nor checks the answers, as before.limited-result-max-page-sizesaid that a page which only some shards answered "ends the result early or leaves hits out without an error". It now says that the next page fails, unlessallow-partial-resultslets it come back with the hits of the shards which kept their contexts.retry-error-codessaid "Queries get the client level attempts only", which a 1.2.0 change made untrue. It now says that a query, count or aggregation is reattempted for up tostorage.read-timetoo, but not the later pages of a result read in pages, and never a search which ran out of its time.Why both the parameter and the check
Measured on single-node clusters of Elasticsearch 7.17.8 and 9.5.4 and OpenSearch 2.19.6 and 3.9.0. Each request read two shards, one of which failed through:
index.routing.allocation.total_shards_per_node1).allow_partial_search_results=falsefailed1, the other shard's hitsPartial shards failurefailed1 (no_shard_available_action_exception). OpenSearch:successful1 of 2,failed0, no failure listedSearch rejected due to missing shards)failed1, the other shard's hits, also where the scroll was opened with the parameterunrecognized parameterfailed1, the other shard's hits_count)failed0unrecognized parametermax)Partial shards failureretry-error-codesclassifies that status, so a transient failure is reattempted like any other. The check'sIOExceptionhas no status, and is permanent.timeoutfails, rather than answering with the hits it found in time. The timeout is one which a direct index query sets as a parameter, or the cluster'ssearch.default_search_timeout.Why counts go through the search API
_countrejectsallow_partial_search_results, so a count which a shard failed could only fail through the check, with a permanentIOException. A search failed for a shard which isn't available got a 503 and was reattempted, while a count of the same query failed at once._countdoesn't tell that a shard ran out of time. With the cluster'ssearch.default_search_timeoutat 1 ms and a slow script query over 20,000 documents,_countanswered 11,533 on Elasticsearch 9.5.4, and 14,336, 12,288 and 18,777 on Elasticsearch 7.17.8, OpenSearch 3.9.0 and Elasticsearch 6.6.0, each with every shard successful. The same count through_searchwith the parameter failed withsearch_timeout_exceptionon Elasticsearch 9.5.4 (429) and 8.15.3 (504).RestCountActionbuilds aSearchRequestwithsize(0),trackTotalHits(true)and theterminate_afterof the count in Elasticsearch 7.17, 8.15 and 9.5 and OpenSearch 1.3 and 3.3. Elasticsearch 6.6 buildssize(0)withouttrackTotalHits(true), as Elasticsearch 6 counts the total by default. The totals agreed with_counton Elasticsearch 6.6.0, 7.17.8, 8.15.3 and 9.5.4 and OpenSearch 2.19.6 and 3.9.0, with and withoutterminate_after._search. That is 0.4% to 10% (0.004 to 0.23 ms) more than through_count. Both run the same search, so the difference lies in the REST layer, which parses a search body and writes the answer of a search.Why an option
count()is too low, adrop()leaves elements behind, and a result ends early.Tests
RestClientShardFailureTest(new, 18 tests), on a mockedRestClient, with the failures which Elasticsearch 9.5.4 reported in the experiments above, shortened:successful1 of 2,failed0);timed_out);_shards;allow_partial_search_results=falseto Elasticsearch 7, 8 and 9 with a search, the first page of a scroll, a page of a point in time, a count and an aggregation, but not with a later page of a scroll; never to Elasticsearch 6; never where partial results are allowed. The first page of a scroll keeps itsscrollparameter, and the count goes to_search.RestClientCountAndAggregationTest: a count is a search ofsize0 withtrack_total_hits, which keepsterminate_afterwhere the count is bounded. It reads the total of Elasticsearch 7 and later, an object, and of Elasticsearch 6, a number.ElasticSearchExceptionConversionTest: the errors of a search which ran out of time convert to permanent, with the bodies Elasticsearch answered: status 429 and 504 (search_timeout_exception), and, in a case of its own, 500 (query_phase_execution_exception"Time exceeded") where 500 is configured as transient. A search rejected with 429 (es_rejected_execution_exception) stays temporary.RestClientRetryTest: the client doesn't reattempt a search which ran out of time, though its status 429 is listed.ElasticSearchScrollTestandElasticSearchPointInTimeTest: a later page which fails, with anIOExceptionor at runtime, releases the scroll or closes the point in time once, and every later call fails with the same exception.SubqueryIteratorTest(janusgraph-core):shouldNeitherProbeNorCacheAfterReadingTheIndexFailedreads an index stream whose ids fail after two and which then answers that they ended, as the point in time did on master. Closing the iterator neither throws, nor asks the stream for another id, nor caches anything, and still closes the stream.ElasticSearchFailedPageCacheTest(new): a scroll and a point in time whose second page fails are read through a realSubqueryIteratorandCaffeineSubqueryCache, as a graph query reads them. The read fails, closing the iterator doesn't throw, and the cache holds nothing.RestClientSetupTest: the option reaches the client,falseby default andtruewhere configured.ElasticsearchIndexTest(Elasticsearch container), on an index of two shards: 10 documents, read in the order oftimein pages of 3.shouldFailAResultWhichSomeShardsLeftOut, in each paging mode, with a script field which throws fordoc7: a single request of 8 hits throws aBackendException, and reading the whole result throws when it reaches the third page. Both failures name the script's exception.shouldEndAResultEarlyWherePartialResultsAreAllowed, in each paging mode: with the option, the result ends at the third page, as before, withoutdoc7anddoc9.shouldFailACountWhichSomeShardsLeftOut: a count whosepost_filterthrows fordoc7fails, naming the script's exception.shouldCountWhatTheShardsWhichAnsweredFoundWherePartialResultsAreAllowed: with the option, it counts fewer than the 10 documents.Mutation checks, each with the rest of the fix in place:
RestClientShardFailureTestfail withExpected java.io.IOException to be thrown, but nothing was thrown, one for each kind of answer above.failedalone, withoutsuccessfulbelowtotal: the test of the answer OpenSearch gives fails.timed_out: the three tests of answers which ran out of time fail. Without it for aggregations alone: the test of the aggregation fails.expected: <[false, false, null, false, false, false]> but was: <[null, null, null, null, null, null]>._count: the test of the parameter fails for the count (null), and the count test withexpected: </store/_search> but was: </store/_count>.testSearchWhichRanOutOfTimeIsNotRetriedfails,Wanted 1 time ... But was 4 times. Classifying it as transient: the three conversion tests of time-outs fail withexpected: <org.janusgraph.diskstorage.PermanentBackendException> but was: <org.janusgraph.diskstorage.TemporaryBackendException>. Recognisingsearch_timeout_exceptionalone: the test of the answer of Elasticsearch 7 (500) fails so.ElasticSearchScrollTestcases fail withWanted but not invoked: elasticSearchClient.deleteScroll("scrollId").SubqueryIterator, or withclose()ignoring it:shouldNeitherProbeNorCacheAfterReadingTheIndexFailedfails withexpected: <false> but was: <true>, as the stream is asked for another id after its failure. BothElasticSearchFailedPageCacheTestcases fail, as closing the iterator throws the failure of the page again:Elasticsearch answered a page of a scroll with the results of only 1 of 2 shardsandCould not read the next page of point in time pit-1.Expected ... to be thrown, but nothing was thrown. Its cache test still passes, as theSubqueryIteratorno longer asks after a failed read.RestClientSetupTestcases fail withWanted but not invoked: restElasticSearchClientMock.setAllowPartialResults(...)._count, which reads as master does, against Elasticsearch 9.5.4:shouldFailAResultWhichSomeShardsLeftOutfails in all three paging modes withExpected org.janusgraph.diskstorage.BackendException to be thrown, but nothing was thrown, as the single request returns the hits of the other shard._countrejects thepost_filterwith 400 rather than failing for the shard.shouldEndAResultEarlyWherePartialResultsAreAllowedpasses there, as it describes master's behaviour.Suites, locally with Java 11, rebased onto #5075 (
75aeea1c2), with checkstyle on:ElasticsearchIndexTest(314),BerkeleyElasticsearchTest(117, 1 skipped) and the PR's 8 unit test classes (118) pass.Before the rebase, locally with Java 11 on
3285cbf18:ElasticsearchIndexTest(314),ElasticsearchConfigTest(12),BerkeleyElasticsearchTest(117, 1 skipped) and the module's 23 unit test classes (242) pass.InMemoryGraphTest(219, 42 skipped), pass.ElasticsearchIndexTestpass:shouldCountZeroForALimitOfZero;testReadFailureWithAStatusInRetryErrorCodesIsTransient, which fails a count with a 400 and a 404.Notes
SubqueryIterator, where this PR adds itsfailedflag. This PR is rebased onto it: the flag is set where Turn the ids of an index query into elements in one pass #5075'scomputeNextreads the next element (findNext()), andclose()checks it before its probe.retry-error-codeslists by default, so the client reattempts itretry-limittimes (3 by default). Where the index query sends it before it returns its stream (its first request, and the first page of a scroll or of a point in time),BackendOperationalso reattempts the index query withinstorage.read-time(10 s by default) before it fails.timeoutfails, where it returned the hits found in time. JanusGraph doesn't reattempt it; the low-levelRestClientstill sends one answered with 504, as on Elasticsearch 8.15.3, to its next host.min_score. The count API rejected a body with anything but the query (request does not support [...]), so such a count failed before.IOExceptionwithout a status, so it is permanent: a later page of a scroll, or any answer of Elasticsearch 6. The later pages are read outsideBackendOperationanyway.scroll-keep-alive. Elasticsearch 7 and later fail such a page themselves and free its contexts. Releasing it would take more than a line or two, for an end-of-life version.RestClientsends a request answered with 502, 503 or 504 to each host before it fails, and takes each for dead for a while: a search on an index with a shard which isn't available (503), and a search which ran out of time on Elasticsearch 8.15.3 (504)._shardsinto a small object where it parsed it into a map. The cost of counts through the search API is measured above.🤖 Generated with Claude Code