All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.action.updatebyquery.TransportUpdateByQueryAction Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.action.updatebyquery;

import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.UnavailableShardsException;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.TimeoutClusterStateListener;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.routing.GroupShardsIterator;
import org.elasticsearch.cluster.routing.ShardIterator;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.node.NodeClosedException;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.*;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;

/**
 * Delegates a {@link IndexUpdateByQueryRequest} to the primary shards of the index this request is targeted to.
 * The requests is transformed into {@link ShardUpdateByQueryRequest} and send to each primary shard. Afterwards
 * the responses from the shards are merged into a final response which is send to the client.
 */
// Perhaps create a base transport update action that sends shard requests to primary shards only
public class TransportUpdateByQueryAction extends TransportAction {

    private final TransportService transportService;

    private final ClusterService clusterService;

    private final TransportShardUpdateByQueryAction transportShardUpdateByQueryAction;

    @Inject
    public TransportUpdateByQueryAction(Settings settings,
                                        ThreadPool threadPool,
                                        TransportService transportService,
                                        ClusterService clusterService,
                                        TransportShardUpdateByQueryAction transportShardUpdateByQueryAction) {
        super(settings, threadPool);
        this.transportService = transportService;
        this.clusterService = clusterService;
        this.transportShardUpdateByQueryAction = transportShardUpdateByQueryAction;
        transportService.registerHandler(UpdateByQueryAction.NAME, new TransportHandler());
    }

    protected void doExecute(UpdateByQueryRequest request, ActionListener listener) {
        long startTime = System.currentTimeMillis();
        MetaData metaData = clusterService.state().metaData();
        String[] concreteIndices = metaData.concreteIndices(request.indices(), IndicesOptions.lenient());
        Map> routingMap = metaData.resolveSearchRouting(request.routing(), request.indices());
        if (concreteIndices.length == 1) {
            doExecuteIndexRequest(request, metaData, concreteIndices[0], routingMap, new SingleIndexUpdateByQueryActionListener(startTime, listener));
        } else {
            MultipleIndexUpdateByQueryActionListener indexActionListener =
                    new MultipleIndexUpdateByQueryActionListener(startTime, listener, concreteIndices.length);
            for (String concreteIndex : concreteIndices) {
                doExecuteIndexRequest(request, metaData, concreteIndex, routingMap, indexActionListener);
            }
        }

    }

    private static class MultipleIndexUpdateByQueryActionListener implements ActionListener {

        private final long startTime;
        private final ActionListener listener;
        private final int expectedNumberOfResponses;
        private final AtomicReferenceArray successFullIndexResponses;
        private final AtomicReferenceArray failedIndexResponses;
        private final AtomicInteger indexCounter;

        private MultipleIndexUpdateByQueryActionListener(long startTime, ActionListener listener, int expectedNumberOfResponses) {
            this.startTime = startTime;
            this.listener = listener;
            successFullIndexResponses = new AtomicReferenceArray(expectedNumberOfResponses);
            failedIndexResponses = new AtomicReferenceArray(expectedNumberOfResponses);
            this.expectedNumberOfResponses = expectedNumberOfResponses;
            indexCounter = new AtomicInteger();
        }

        public void onResponse(IndexUpdateByQueryResponse indexUpdateByQueryResponse) {
            successFullIndexResponses.set(indexCounter.getAndIncrement(), indexUpdateByQueryResponse);
            if (indexCounter.get() == expectedNumberOfResponses) {
                finishHim();

            }
        }

        public void onFailure(Throwable e) {
            failedIndexResponses.set(indexCounter.getAndIncrement(), e);
            if (indexCounter.get() == expectedNumberOfResponses) {
                finishHim();
            }
        }

        private void finishHim() {
            long tookInMillis = System.currentTimeMillis() - startTime;
            UpdateByQueryResponse response = new UpdateByQueryResponse(tookInMillis);
            List indexResponses = Lists.newArrayList();
            List indexFailures = Lists.newArrayList();
            for (int i = 0; i < expectedNumberOfResponses; i++) {
                IndexUpdateByQueryResponse indexResponse = successFullIndexResponses.get(i);
                if (indexResponse != null) {
                    indexResponses.add(indexResponse);
                } else {
                    indexFailures.add(ExceptionsHelper.detailedMessage(failedIndexResponses.get(i)));
                }
            }
            response.indexResponses(indexResponses.toArray(new IndexUpdateByQueryResponse[indexResponses.size()]));
            response.mainFailures(indexFailures.toArray(new String[indexFailures.size()]));
            listener.onResponse(response);
        }
    }

    private static class SingleIndexUpdateByQueryActionListener implements ActionListener {

        private final long startTime;
        private final ActionListener listener;

        private SingleIndexUpdateByQueryActionListener(long startTime, ActionListener listener) {
            this.listener = listener;
            this.startTime = startTime;
        }

        public void onResponse(IndexUpdateByQueryResponse indexUpdateByQueryResponse) {
            long tookInMillis = System.currentTimeMillis() - startTime;
            UpdateByQueryResponse finalResponse = new UpdateByQueryResponse(tookInMillis, indexUpdateByQueryResponse);
            listener.onResponse(finalResponse);
        }

        public void onFailure(Throwable e) {
            long tookInMillis = System.currentTimeMillis() - startTime;
            UpdateByQueryResponse finalResponse = new UpdateByQueryResponse(tookInMillis);
            finalResponse.mainFailures(new String[]{ExceptionsHelper.detailedMessage(e)});
            listener.onResponse(finalResponse);
        }

    }


    // Index operations happen below here:

    protected void doExecuteIndexRequest(UpdateByQueryRequest request, MetaData metaData, String concreteIndex,
                                         @Nullable Map> routingMap, ActionListener listener) {
        String[] filteringAliases = metaData.filteringAliases(concreteIndex, request.indices());
        Set routing = null;
        if (routingMap != null) {
            routing = routingMap.get(concreteIndex);
        }
        IndexUpdateByQueryRequest indexRequest = new IndexUpdateByQueryRequest(request, concreteIndex, filteringAliases, routing);
        new UpdateByQueryIndexOperationAction(indexRequest, listener).startExecution();
    }

    private class UpdateByQueryIndexOperationAction {

        final IndexUpdateByQueryRequest request;
        final ActionListener indexActionListener;

        private UpdateByQueryIndexOperationAction(IndexUpdateByQueryRequest request, ActionListener listener) {
            this.request = request;
            this.indexActionListener = listener;
        }

        void startExecution() {
            startExecution(false);
        }

        boolean startExecution(boolean fromClusterEvent) {
            ClusterState state = clusterService.state();
            ClusterBlockException blockException = state.blocks().globalBlockedException(ClusterBlockLevel.WRITE);
            if (blockException != null) {
                logger.trace("[{}] global block exception, retrying...", request.index());
                overallRetry(fromClusterEvent, null, blockException);
                return false;
            }
            blockException = state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, request.index());
            if (blockException != null) {
                logger.trace("[{}] index block exception, retrying...", request.index());
                overallRetry(fromClusterEvent, null, blockException);
                return false;
            }

            List primaryShards = Lists.newArrayList();
            GroupShardsIterator groupShardsIterator =
                    clusterService.operationRouting().deleteByQueryShards(state, request.index(), request.routing());
            for (ShardIterator shardIt : groupShardsIterator) {
                for (ShardRouting shardRouting = shardIt.nextOrNull(); shardRouting != null; shardRouting = shardIt.nextOrNull()) {
                    if (shardRouting.primary()) {
                        if (shardRouting.started()) {
                            primaryShards.add(shardRouting);
                        } else {
                            logger.trace(
                                    "[{}][{}] required primary shard isn't available, retrying...",
                                    request.index(), shardRouting.id()
                            );
                            overallRetry(fromClusterEvent, shardRouting, null);
                            return false;
                        }
                    }
                }
            }

            if (primaryShards.size() != groupShardsIterator.size()) {
                logger.trace(
                        "[{}] not all required primary shards[{}/{}] are available for index[{}], retrying...",
                        request.index(), primaryShards.size(), groupShardsIterator.size()
                );
                overallRetry(fromClusterEvent, null, null);
                return false;
            }

            logger.trace("[{}] executing IndexUpdateByQueryRequest", request.index());
            DiscoveryNodes nodes = state.nodes();
            final ShardResponseListener responseListener = new ShardResponseListener(primaryShards.size(), indexActionListener);
            for (ShardRouting shard : primaryShards) {
                logger.trace("[{}][{}] executing ShardUpdateByQueryRequest", request.index(), shard.id());
                if (shard.currentNodeId().equals(nodes.localNodeId())) {
                    executeLocally(shard, responseListener);
                } else {
                    executeRemotely(shard, nodes, responseListener);
                }
            }

            return true;
        }

        void overallRetry(boolean fromClusterEvent, final ShardRouting shardRouting, @Nullable final Throwable failure) {
            if (!fromClusterEvent) {
                clusterService.add(request.timeout(), new TimeoutClusterStateListener() {

                    public void postAdded() {
                        logger.trace("[{}] post added, retrying update by query", request.index());
                        if (startExecution(true)) {
                            // if we managed to start and perform the operation on the primary, we can remove this listener
                            clusterService.remove(this);
                        }
                    }

                    public void onClose() {
                        logger.trace("[{}] update by query for, node closed", request.index());
                        clusterService.remove(this);
                        indexActionListener.onFailure(new NodeClosedException(clusterService.localNode()));
                    }

                    public void clusterChanged(ClusterChangedEvent event) {
                        logger.trace("[{}] cluster changed, retrying update by query", request.index());
                        if (startExecution(true)) {
                            // if we managed to start and perform the operation on the primary, we can remove this listener
                            clusterService.remove(this);
                        }
                    }

                    public void onTimeout(TimeValue timeValue) {
                        // just to be on the safe side, see if we can start it now?
                        logger.trace("[{}] timeout, retrying update by query", request.index());
                        if (startExecution(true)) {
                            clusterService.remove(this);
                            return;
                        }
                        clusterService.remove(this);
                        Throwable listenerFailure = failure;
                        if (listenerFailure == null) {
                            if (shardRouting == null) {
                                listenerFailure = new UnavailableShardsException(null, "no available shards: Timeout waiting for [" + timeValue + "], request: " + request.toString());
                            } else {
                                listenerFailure = new UnavailableShardsException(shardRouting.shardId(), "[" + shardRouting.shardId() + "] not started, Timeout waiting for [" + timeValue + "], request: " + request.toString());
                            }
                        }
                        indexActionListener.onFailure(listenerFailure);
                    }
                });
            }
        }

        void executeLocally(final ShardRouting shard, final ShardResponseListener responseListener) {
            final ShardUpdateByQueryRequest localShardRequest = new ShardUpdateByQueryRequest(request, shard.id(), shard.currentNodeId());
            transportShardUpdateByQueryAction.execute(localShardRequest, new ActionListener() {

                public void onResponse(ShardUpdateByQueryResponse shardUpdateByQueryResponse) {
                    responseListener.handleResponse(shardUpdateByQueryResponse);
                }

                public void onFailure(Throwable e) {
                    responseListener.handleException(e, shard);
                }
            });
        }

        void executeRemotely(final ShardRouting shard, DiscoveryNodes nodes, final ShardResponseListener responseListener) {
            final DiscoveryNode discoveryNode = nodes.get(shard.currentNodeId());
            if (discoveryNode == null) {
                responseListener.handleException(new RuntimeException("No node for shard"), shard);
                return;
            }

            ShardUpdateByQueryRequest localShardRequest = new ShardUpdateByQueryRequest(request, shard.id(), shard.currentNodeId());
            transportService.sendRequest(discoveryNode, TransportShardUpdateByQueryAction.ACTION_NAME,
                    localShardRequest, new BaseTransportResponseHandler() {

                public ShardUpdateByQueryResponse newInstance() {
                    return new ShardUpdateByQueryResponse();
                }

                public void handleResponse(ShardUpdateByQueryResponse response) {
                    responseListener.handleResponse(response);
                }

                public void handleException(TransportException e) {
                    responseListener.handleException(e, shard);
                }

                public String executor() {
                    return ThreadPool.Names.SAME;
                }
            });
        }

        private class ShardResponseListener {

            final AtomicReferenceArray shardResponses;

            final AtomicInteger indexCounter;

            final ActionListener finalListener;

            final int numberOfExpectedShardResponses;

            private ShardResponseListener(int numberOfPrimaryShards, ActionListener finalListener) {
                shardResponses = new AtomicReferenceArray(numberOfPrimaryShards);
                numberOfExpectedShardResponses = numberOfPrimaryShards;
                indexCounter = new AtomicInteger();
                this.finalListener = finalListener;
            }

            void handleResponse(ShardUpdateByQueryResponse response) {
                shardResponses.set(indexCounter.getAndIncrement(), response);
                if (indexCounter.get() == numberOfExpectedShardResponses) {
                    finalizeAction();
                }
            }

            void handleException(Throwable e, ShardRouting shard) {
                logger.error("[{}][{}] error while executing update by query shard request", e, request.index(), shard.id());
                String failure = ExceptionsHelper.detailedMessage(e);
                shardResponses.set(indexCounter.getAndIncrement(), new ShardUpdateByQueryResponse(shard.id(), failure));
                if (indexCounter.get() == numberOfExpectedShardResponses) {
                    finalizeAction();
                }
            }

            void finalizeAction() {
                ShardUpdateByQueryResponse[] responses = new ShardUpdateByQueryResponse[shardResponses.length()];
                for (int i = 0; i < shardResponses.length(); i++) {
                    responses[i] = shardResponses.get(i);
                }
                IndexUpdateByQueryResponse finalResponse = new IndexUpdateByQueryResponse(
                        request.index(),
                        responses
                );
                finalListener.onResponse(finalResponse);
            }

        }

    }

    private class TransportHandler extends BaseTransportRequestHandler {

        public UpdateByQueryRequest newInstance() {
            return new UpdateByQueryRequest();
        }

        public String executor() {
            return ThreadPool.Names.SAME;
        }

        public void messageReceived(UpdateByQueryRequest request, final TransportChannel channel) throws Exception {
            // no need to have a threaded listener since we just send back a response
            request.listenerThreaded(false);
            doExecute(request, new ActionListener() {

                public void onResponse(UpdateByQueryResponse result) {
                    try {
                        channel.sendResponse(result);
                    } catch (Exception e) {
                        onFailure(e);
                    }
                }

                public void onFailure(Throwable e) {
                    try {
                        channel.sendResponse(e);
                    } catch (Exception e1) {
                        logger.warn("Failed to send response for get", e1);
                    }
                }
            });
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy