Skip to content

Conversation

DaveCTurner
Copy link
Contributor

Each TransportTasksAction fans-out to multiple nodes, accumulates responses and retains them until all the nodes have responded, and then converts the responses into a final result.

Similarly to #92987 and #93484, we should accumulate the responses in a structure that doesn't require so much copying later on, and should drop the received responses if the task is cancelled while some nodes' responses are still pending.

Each `TransportTasksAction` fans-out to multiple nodes, accumulates
responses and retains them until all the nodes have responded, and then
converts the responses into a final result.

Similarly to elastic#92987 and elastic#93484, we should accumulate the responses in a
structure that doesn't require so much copying later on, and should drop
the received responses if the task is cancelled while some nodes'
responses are still pending.
@DaveCTurner DaveCTurner added >bug :Distributed Coordination/Task Management Issues for anything around the Tasks API - both persistent and node level. v8.9.0 labels May 23, 2023
@elasticsearchmachine elasticsearchmachine added the Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. label May 23, 2023
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-distributed (Team:Distributed)

@elasticsearchmachine
Copy link
Collaborator

Hi @DaveCTurner, I've created a changelog YAML for you.

DaveCTurner added a commit to DaveCTurner/elasticsearch that referenced this pull request May 23, 2023
In a busy cluster the list-tasks API may retain information about a very
large number of tasks while waiting for all nodes to respond. This
commit makes the API cancellable so that unnecessary partial results can
be released earlier.

Relates elastic#96279, which implements the early-release functionality.
Copy link
Contributor

@arteam arteam left a comment

Choose a reason for hiding this comment

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

LGTM! I've left a few cosmeticcomments


reachabilityChecker.ensureUnreachable();

while (true) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to use while (taskResponseListeners.peek() != null) here instead of manually breaking out of the loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That'd work, but I'd rather just do a single read, avoiding having to reason about the relationship between peek() and poll().

return;
}

logger.debug(Strings.format("failed to execute on node [{}]", nodeId), e);
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering if can perform formatting lazily by using a supplier : logger.debug(() -> Strings.format(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes; it's not very common to get here, but I'll do that.

@Override
public void onResponse(NodeTasksResponse nodeResponse) {
synchronized (taskResponses) {
taskResponses.addAll(nodeResponse.results);
Copy link
Contributor

Choose a reason for hiding this comment

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

I've run some tests and have seen a lot of cases where nodeResponse.results is empty. Would it make sense to perform an isEmpty check on nodeResponse.results before acquiring the lock on taskResponses?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes that makes sense.

private void nodeOperation(CancellableTask task, NodeTaskRequest nodeTaskRequest, ActionListener<NodeTasksResponse> listener) {
TasksRequest request = nodeTaskRequest.tasksRequest;
processTasks(request, ActionListener.wrap(tasks -> nodeOperation(task, listener, request, tasks), listener::onFailure));
final var taskResponses = new ArrayList<TaskResponse>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to pre-size taskResponses based on the amount of the nodes from which we collect responses?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think the number of nodes would be a particularly useful estimate for this - as you say, many nodes return nothing, but sometimes they will return thousands of results. These are pretty low-throughput APIs so I think the default sizing is ok.

@DaveCTurner DaveCTurner added auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) and removed auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) labels May 25, 2023
@DaveCTurner
Copy link
Contributor Author

@elasticmachine update branch

Copy link
Contributor

@henningandersen henningandersen left a comment

Choose a reason for hiding this comment

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

LGTM.

Slightly complex (but good), I wonder if a comment or two on how the result lists are captured locally and how cancelling ensures it is freed could make sense?

DaveCTurner added a commit to DaveCTurner/elasticsearch that referenced this pull request May 26, 2023
We have this somewhat-complex pattern in 3 places already, and elastic#96279
will introduce a couple more, so this commit extracts it as a dedicated
utility.

Relates elastic#92987
Relates elastic#93484
@DaveCTurner
Copy link
Contributor Author

Yeah it is a bit convoluted isn't it? I extracted a utility (and added more commentary) in #96373, and will update this PR to use it once that's merged.

DaveCTurner added a commit that referenced this pull request May 26, 2023
We have this somewhat-complex pattern in 3 places already, and #96279
will introduce a couple more, so this commit extracts it as a dedicated
utility.

Relates #92987
Relates #93484
@DaveCTurner DaveCTurner added the auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) label May 30, 2023
@elasticsearchmachine elasticsearchmachine merged commit 2513104 into elastic:main May 30, 2023
@DaveCTurner DaveCTurner deleted the 2023-05-23-TransportTasksAction-cancellability branch May 30, 2023 06:51
DaveCTurner added a commit that referenced this pull request May 30, 2023
In a busy cluster the list-tasks API may retain information about a very
large number of tasks while waiting for all nodes to respond. This
commit makes the API cancellable so that unnecessary partial results can
be released earlier.

Relates #96279, which implements the early-release functionality.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) >bug :Distributed Coordination/Task Management Issues for anything around the Tasks API - both persistent and node level. Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. v8.9.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants