Surface PIT-context exhaustion with an actionable error message#5631
Surface PIT-context exhaustion with an actionable error message#5631ahkcs wants to merge 2 commits into
Conversation
PR Reviewer Guide 🔍(Review updated until commit 80125e0)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 80125e0 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit c349d1e
Suggestions up to commit 47d726a
Suggestions up to commit 2b9f8d9
Suggestions up to commit 0028fa9
Suggestions up to commit 096619e
|
|
Persistent review updated to latest commit b9c7410 |
b9c7410 to
096619e
Compare
|
Persistent review updated to latest commit 096619e |
096619e to
0028fa9
Compare
|
Persistent review updated to latest commit 0028fa9 |
0028fa9 to
2b9f8d9
Compare
|
Persistent review updated to latest commit 2b9f8d9 |
2b9f8d9 to
47d726a
Compare
|
Persistent review updated to latest commit 47d726a |
The Calcite engine opens a Point-In-Time (PIT) context to page over a query it cannot push down to OpenSearch -- for example a stats/aggregation that groups by a text field with no keyword sub-field, which forces a full doc scan. A PIT allocates one reader context per shard, so a single such query over a many-shard index can exhaust the node's search.max_open_pit_context budget on its own; concurrent load makes it more likely. Previously that failure surfaced to the user as the opaque internal message "exception while executing query: Error occurred while creating PIT for internal plugin operation" with no hint of the cause or the remedy. Detect the PIT-context-limit rejection in the execute-time cause chain and rethrow it as a PointInTimeLimitExceededException wrapped in an ErrorReport, so the reason names the search.max_open_pit_context setting and the details explain the two remedies (raise the setting, or optimize the query). Match on the "too many Point In Time contexts" marker rather than the exception class, since OpenSearchRejectedExecutionException is also raised for scroll and thread-pool rejections. The scan walks the whole cause chain because the rejection surfaces several layers deep (SQLException -> RuntimeException -> ExecutionException -> per-shard rejection). This targets the Calcite path only; the marker check guards against self-referential cause loops. Signed-off-by: Kai Huang <ahkcs@amazon.com>
47d726a to
c349d1e
Compare
|
Persistent review updated to latest commit c349d1e |
| .details( | ||
| "This query opened a Point-In-Time (PIT) context on each shard and reached" | ||
| + " the limit set by [search.max_open_pit_context]. Increase that" | ||
| + " setting, or optimize the query to reduce the number of contexts it" |
There was a problem hiding this comment.
Remove "or optimize the query to reduce the number of contexts it needs"
| * by a text field with no {@code keyword} sub-field -- so a single query over a many-shard index | ||
| * can exhaust the per-node budget on its own. Its message is the customer-facing {@code reason}. | ||
| */ | ||
| public class PointInTimeLimitExceededException extends RuntimeException { |
There was a problem hiding this comment.
[non blocker] Is it best practice to define PointInTimeLimitExceededException for each error message type?
There was a problem hiding this comment.
response type changes from PointInTimeLimitExceededException → ResourceLimitExceededException to be more general
…tion Drop the "optimize the query" remedy from the PIT-context-limit details, leaving the actionable "increase [search.max_open_pit_context]" instruction. Replace the one-message PointInTimeLimitExceededException with a reusable ResourceLimitExceededException in common/error, alongside ErrorReport and ErrorCode.RESOURCE_LIMIT_EXCEEDED, so the type is not one-class-per-message and is reachable across modules. Signed-off-by: Kai Huang <ahkcs@amazon.com>
|
Persistent review updated to latest commit 80125e0 |
Description
The Calcite engine opens an internal Point-in-Time (PIT) context to page over a query it cannot push down to OpenSearch. A common case: a
stats/aggregation that groups by a text field with nokeywordsub-field — aggregations only push down on keyword/numeric/date fields, so the engine falls back to scanning the docs, which opens a PIT.A PIT allocates one reader context per shard, and the node caps the total at
search.max_open_pit_context(default 300). So a single such query over a many-shard index can exhaust the budget on its own; concurrent load makes it more likely.Previously that failure surfaced to the user as an opaque internal message:
{ "error": { "reason": "java.sql.SQLException: exception while executing query: Error occurred while creating PIT for internal plugin operation", "type": "RuntimeException", "code": "UNKNOWN" }, "status": 500 }— no hint of the cause or the remedy.
What it does
Detect the PIT-context-limit rejection in the execute-time cause chain (
OpenSearchExecutionEngine.execute) and rethrow it as aPointInTimeLimitExceededExceptionwrapped in anErrorReport. The user now gets:{ "error": { "reason": "Too many open Point-In-Time (PIT) contexts on this node.", "details": "This query opened a Point-In-Time (PIT) context on each shard and reached the limit set by [search.max_open_pit_context]. Increase that setting, or optimize the query to reduce the number of contexts it needs.", "code": "RESOURCE_LIMIT_EXCEEDED", "type": "PointInTimeLimitExceededException" }, "status": 500 }reasonis a short title; the explanation and the two remedies (raise the setting, or optimize the query) live indetails.codeisRESOURCE_LIMIT_EXCEEDEDfor programmatic handling.Why match on the message, not the exception class:
OpenSearchRejectedExecutionExceptionis also raised for scroll-context and thread-pool rejections, so the class alone can't identify PIT-context exhaustion. Thetoo many Point In Time contextssubstring (authored by OpenSearchSearchService) is the reliable discriminator.Why walk the whole cause chain: at execute time the rejection surfaces several layers deep (
SQLException→RuntimeException→ExecutionException"all shards failed" → per-shardOpenSearchRejectedExecutionException), so detection scans every cause. The walk guards against self-referential cause loops.Why the Calcite path specifically: Calcite is the default engine, and its execution errors are wrapped in an
ErrorReportthat the error formatter (ErrorMessageFactory) short-circuits before it reaches the OpenSearch-exception formatter — so the fix belongs where theErrorReportis built, not in the shared formatter.Scope note
This is a diagnostics-only change. It does not alter PIT routing, the create/delete PIT logic, pushdown behavior, or any other exception handling — every non-exhaustion failure still falls through to the existing path unchanged. The only difference is that this one common, previously-opaque failure now produces a message a user can act on.
Testing
Unit —
OpenSearchExecutionEngineTest:Manual end-to-end: built the plugin, created a 5-shard index with a text-only field (no
keyword), setsearch.max_open_pit_contextto0, and ransource=<index> | stats count() by <text-field>on the Calcite path. The response is the actionable error above (confirmedreason/details/code/type); a pushdown-able aggregation on a keyword field still returns 200 with no PIT.:opensearch:testfor the touched class passes;spotlessJavaCheckis clean on:opensearch.Check List
--signoffor-s.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.