-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Limit concurrent node requests #122850
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
Limit concurrent node requests #122850
Conversation
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/QueryPragmas.java
Show resolved
Hide resolved
| nodeToShardIds.computeIfAbsent(selectedNode, unused -> new ArrayList<>()).add(shard.shardId); | ||
|
|
||
| if (concurrentRequests == null || concurrentRequests.tryAcquire()) { | ||
| if (nodePermits.get(node).tryAcquire()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if instead we want to check all pending requests here before attempting a new one?
Or possibly implement a more complex strategy
|
Pinging @elastic/es-analytical-engine (Team:Analytics) |
# Conflicts: # x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/DataNodeComputeHandler.java
nik9000
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds neat. I'm not deep enough in the request sender to give a good response about how right it is. It'd take a ton of reading. @dnhatn, can you comment?
| if (exchangeSource.isCompleted()) { | ||
| nodeListener.onResponse(new DataNodeComputeResponse(List.of(), Map.of())); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part prevents us from sending a query to remaining data nodes if we collected enough results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: There's one thing here: We'll "skip" it with onSkip(), but the Sender will still continue processing all shards. From what I see, it will continue calling this after every node finishes.
Should we instead pass something to the sender so it stops calling sendRequest()? I don't think it matters, computationally speaking, but it fells like we're doing "too much" when we could shortcircuit instead (?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we won't send more requests because we do:
if (skipRemaining) {
DataNodeRequestSender.this.skipRemaining = true;
}
So we'll only count the number of shards we skip and that's it. I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we won't send more requests because we do:
Correct, this was added today in: a5084ab
So we'll only count the number of shards we skip and that's it. I think.
The total skipped count consists of ones we skipped already (skippedShards) and count of shards we have not processed (remaining shards in pendingShardIds), please see
Line 96 in 50da087
| var skipped = skippedShards.get() + pendingShardIds.size(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these computations should not be expensive, I wonder if we should skip only here, not shortcutting in other places. The reason is that we might need to be more careful not to shortcut in other places when allow_partial_results=true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. It would also simplify the change. We can always add it back later if we see it is needed.
dnhatn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @idegtiarenko. I've some minor comments. The early termination part will be useful, but I couldn't find a use case for limiting concurrent nodes per cluster, except for testing.
| } | ||
|
|
||
| public boolean isCompleted() { | ||
| return completed; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use buffer.isFinished here instead adding a new variable?
...ugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ManyShardsIT.java
Show resolved
Hide resolved
| this.esqlExecutor = esqlExecutor; | ||
| this.rootTask = rootTask; | ||
| this.allowPartialResults = allowPartialResults; | ||
| this.concurrentRequests = concurrentRequests > 0 ? new Semaphore(concurrentRequests) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we initialize the Semaphore for the -1 case with new Semaphore(Integer.MAX_VALUE)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a special case not to keep it at all if we have no limit (most of the cases). But I can do that as well.
# Conflicts: # x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSender.java # x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSenderTests.java
ivancea
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
| /** | ||
| * The maximum number of nodes to be queried at once by this query. This is safeguard to avoid overloading the cluster. | ||
| */ | ||
| public int maxConcurrentNodePerCluster() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public int maxConcurrentNodePerCluster() { | |
| public int maxConcurrentNodesPerCluster() { |
| if (exchangeSource.isCompleted()) { | ||
| nodeListener.onResponse(new DataNodeComputeResponse(List.of(), Map.of())); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: There's one thing here: We'll "skip" it with onSkip(), but the Sender will still continue processing all shards. From what I see, it will continue calling this after every node finishes.
Should we instead pass something to the sender so it stops calling sendRequest()? I don't think it matters, computationally speaking, but it fells like we're doing "too much" when we could shortcircuit instead (?)
| }); | ||
| safeGet(future); | ||
| assertThat(sent.size(), equalTo(5)); | ||
| assertThat(maxConcurrentRequests.get(), equalTo(2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would randomize this 2 if possible.
At least, to test some edge cases, like 1 and 5.
| assertThat(sent.size(), equalTo(2));// onResponse() + onSkip() | ||
| assertThat(response.totalShards, equalTo(5)); | ||
| assertThat(response.successfulShards, equalTo(5)); | ||
| assertThat(response.failedShards, equalTo(0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should count skipped shards as successful or as org.elasticsearch.xpack.esql.plugin.ComputeResponse#skippedShards
nik9000
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good. Let's get Nhat's 👍 too.
| if (skipRemaining) { | ||
| DataNodeRequestSender.this.skipRemaining = true; | ||
| } | ||
| onAfter(List.of()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should clear the shard failures for shards that are skipped; otherwise, we will still report failures.
| if (exchangeSource.isCompleted()) { | ||
| nodeListener.onResponse(new DataNodeComputeResponse(List.of(), Map.of())); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these computations should not be expensive, I wonder if we should skip only here, not shortcutting in other places. The reason is that we might need to be more careful not to shortcut in other places when allow_partial_results=true.
dnhatn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks @idegtiarenko
This adds a possibility to limit amount of nodes a single query send requests at once.
This could be useful: