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

com.microsoft.azure.documentdb.internal.query.ParallelDocumentQueryExecutionContextBase Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 * Copyright (c) 2017 Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.microsoft.azure.documentdb.internal.query;

import static com.microsoft.azure.documentdb.internal.query.ExceptionHelper.toRuntimeException;
import static com.microsoft.azure.documentdb.internal.query.ExceptionHelper.unwrap;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.microsoft.azure.documentdb.Document;
import com.microsoft.azure.documentdb.DocumentClientException;
import com.microsoft.azure.documentdb.DocumentQueryClientInternal;
import com.microsoft.azure.documentdb.FeedOptions;
import com.microsoft.azure.documentdb.PartitionKeyRange;
import com.microsoft.azure.documentdb.SqlQuerySpec;
import com.microsoft.azure.documentdb.internal.DocumentServiceRequest;
import com.microsoft.azure.documentdb.internal.DocumentServiceResponse;
import com.microsoft.azure.documentdb.internal.HttpConstants;
import com.microsoft.azure.documentdb.internal.RequestChargeTracker;
import com.microsoft.azure.documentdb.internal.ResourceType;
import com.microsoft.azure.documentdb.internal.Utils;
import com.microsoft.azure.documentdb.internal.query.funcs.Callback3;
import com.microsoft.azure.documentdb.internal.query.funcs.Func1;
import com.microsoft.azure.documentdb.internal.query.funcs.Func2;
import com.microsoft.azure.documentdb.internal.routing.Range;
import com.microsoft.azure.documentdb.internal.routing.RoutingMapProvider;
import com.microsoft.azure.documentdb.internal.routing.RoutingMapProviderHelper;

abstract class ParallelDocumentQueryExecutionContextBase extends AbstractQueryExecutionContext {
    private static final int NUMBER_OF_NETWORK_CALLS_PER_PROCESSORS = 10;
    private static final int DEFAULT_MAX_BUFFER_SIZE = 1000;

    // TODO: this should move to #OrderByQueryExecutionContext
    private static final String FORMAT_PLACE_HOLDER = "{documentdb-formattableorderbyquery-filter}";
    protected final Comparator> defaultComparator = new Comparator>() {

        @Override
        public int compare(DocumentProducer producer1,
                DocumentProducer producer2) {
            return producer1.getTargetRange().getMinInclusive().compareTo(producer2.getTargetRange().getMinInclusive());

        }
    };

    private final Class documentProducerClassT;
    private final FetchScheduler fetchScheduler;

    protected final Logger LOGGER;
    protected final String collectionSelfLink;

    private final Func1 executeFunc;

    // DocumentProducers
    protected Vector> documentProducers;
    // Caps
    private int maxDegreeOfParallelism;
    // Helper member fields
    protected final RequestChargeTracker chargeTracker;
    // Futures for initialization and scheduling
    protected Future initializationFuture;
    // Future for document producers
    protected final ExecutorService executorService;
    //TODO init
    protected boolean shouldPrefetch;

    private final Callback3, Integer, Double> fetchCompletionCallback;
    private final int actualMaxBufferedItemCount;
    private final AtomicInteger totalBufferedItems;

    public ParallelDocumentQueryExecutionContextBase(DocumentQueryClientInternal client,
            String collectionSelfLink,
            SqlQuerySpec querySpec,
            FeedOptions options, String resourceLink, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo,
            Class documentProducerClassT) {
        super(client, ResourceType.Document, documentProducerClassT,
                partitionedQueryExecutionInfo.getQueryInfo().hasRewrittenQuery() ?
                        // Hardcode formattable filter to true for now
                        new SqlQuerySpec(partitionedQueryExecutionInfo.getQueryInfo()
                                .getRewrittenQuery().replace(FORMAT_PLACE_HOLDER, "true"), querySpec.getParameters()) :
                                    querySpec,
                                    options, resourceLink);
        this.collectionSelfLink = collectionSelfLink;
        Collection ranges = this.getTargetPartitionKeyRanges(
                partitionedQueryExecutionInfo.getQueryRanges());
        this.documentProducers = new Vector>(ranges.size());

        this.executorService = client.getExecutorService();
        this.documentProducerClassT = documentProducerClassT;
        LOGGER = LoggerFactory.getLogger(this.getClass());

        this.shouldPrefetch = options.getMaxDegreeOfParallelism() != 0;

        if (options.getMaxDegreeOfParallelism() >= 0) {
            this.maxDegreeOfParallelism = Math.min(ranges.size(), options.getMaxDegreeOfParallelism());
        } else {
            // auto scale the degree of parallelism
            int cores = Utils.getConcurrencyFactor();
            this.maxDegreeOfParallelism = Math.min(ranges.size(), NUMBER_OF_NETWORK_CALLS_PER_PROCESSORS * cores);
        }
        this.maxDegreeOfParallelism = Math.max(this.maxDegreeOfParallelism, 1);
        this.fetchScheduler = new FetchScheduler(this.executorService, maxDegreeOfParallelism);

        this.executeFunc = new Func1() {
            @Override
            public DocumentServiceResponse apply(DocumentServiceRequest request) throws DocumentClientException {
                return executeRequest(request);
            }
        };

        this.chargeTracker = new RequestChargeTracker();
        this.actualMaxBufferedItemCount = Math.max(options.getMaxBufferedItemCount(), DEFAULT_MAX_BUFFER_SIZE);
        this.totalBufferedItems = new AtomicInteger(0);
        this.fetchCompletionCallback = new Callback3, Integer, Double>() {

            @Override
            public void run(DocumentProducer producer, Integer cnt, Double requestCharge) throws Exception {
                chargeTracker.addCharge(requestCharge);
                totalBufferedItems.addAndGet(cnt);

                if (!producer.fetchedAll()) {
                    if (shouldPrefetch && (actualMaxBufferedItemCount - totalBufferedItems.get() > 0)) {
                        producer.tryScheduleFetch();
                    }
                }
            }
        };
    }

    protected Future initializeAsync(PartitionedQueryExecutionInfo partitionedQueryExecutionInfo, final int initialPageSize,
            final Class documentProducerClassT,
            final Collection ranges,
            final FeedOptions options) throws InterruptedException, ExecutionException, Exception {
        final ParallelDocumentQueryExecutionContextBase that = this;
        Callable callable = new Callable() {

            @Override
            public Void call() throws Exception {
                for (final PartitionKeyRange range : ranges) {
                    DocumentProducer docProducer = new DocumentProducer(
                            that.executeFunc,
                            new Func2() {

                                @Override
                                public DocumentServiceRequest apply(String continuationToken, Integer pageSize) {
                                    DocumentServiceRequest request = that.createRequest(that.getFeedHeaders(that.options), that.querySpec, range.getId());
                                    request.getHeaders().put(HttpConstants.HttpHeaders.PAGE_SIZE, Integer.toString(pageSize));
                                    request.getHeaders().put(HttpConstants.HttpHeaders.CONTINUATION, continuationToken);
                                    return request;
                                }
                            },
                            range,
                            documentProducerClassT,
                            that.fetchScheduler, initialPageSize, null, fetchCompletionCallback);

                    if (that.shouldPrefetch) {
                        docProducer.tryScheduleFetch();
                    }

                    that.documentProducers.add(docProducer);
                }

                return null;
            }
        };
        return this.executorService.submit(callable);
    }

    protected Collection getTargetPartitionKeyRanges(List> providedRanges) {
        return RoutingMapProviderHelper.getOverlappingRanges(this.client.getPartitionKeyRangeCache(), this.collectionSelfLink,
                providedRanges);
    }

    @Override
    protected void finalize() throws Throwable {
        this.fetchScheduler.stop();
        this.initializationFuture.cancel(true);

        notifyStopDocumentProducers();
    }

    @Override
    public List fetchNextBlock() throws DocumentClientException {
        throw new UnsupportedOperationException("fetchNextBlock");
    }

    @Override
    public boolean hasNext() {
        try {
            this.initializationFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.warn("Failed to initialize. ", e);
            throw toRuntimeException(unwrap(e));
        }

        return hasNextInternal();
    }

    public abstract T nextInternal() throws Exception;

    @Override
    public T next() {
        if (!this.hasNext()) {
            throw new NoSuchElementException("next");
        }

        try {
            this.initializationFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.warn("Failed to initialize. ", e);
            throw toRuntimeException(unwrap(e));
        }

        if (super.responseHeaders == null) {
            super.responseHeaders = new HashMap();
        }

        try {
            T result = nextInternal();
            this.totalBufferedItems.decrementAndGet();
            return result;
        } catch (NoSuchElementException e) {
            throw e;
        } catch (Exception e) {
            throw toRuntimeException(unwrap(e));
        }
    }

    protected List getReplacementRanges(PartitionKeyRange targetRange, String collectionSelfLink) {
        RoutingMapProvider routingMapProvider =  this.client.getPartitionKeyRangeCache();
        List replacementRanges = Collections.list(Collections.enumeration(
                routingMapProvider.getOverlappingRanges(collectionSelfLink, targetRange.toRange(), true)));
        String replaceMinInclusive = replacementRanges.get(0).getMinInclusive();
        String replaceMaxExclusive = replacementRanges.get(replacementRanges.size()-1).getMaxExclusive();
        if (!replaceMinInclusive.equals(targetRange.getMinInclusive())
                || !replaceMaxExclusive.equals(targetRange.getMaxExclusive())) {
            throw new IllegalStateException(String.format(
                    "Target range and Replacement range has mismatched min/max. Target range: [%s, %s). Replacement range: [%s, %s).",
                    targetRange.getMinInclusive(),
                    targetRange.getMaxExclusive(),
                    replaceMinInclusive,
                    replaceMaxExclusive));
        }

        return replacementRanges;
    }

    private boolean needPartitionKeyRangeCacheRefresh(Exception ex) {

        Throwable t = ExceptionHelper.unwrap(ex);
        if (t instanceof DocumentClientException) {
            DocumentClientException clientException = (DocumentClientException) t;
            return (clientException.getStatusCode() == HttpStatus.SC_GONE && clientException.getSubStatusCode() != null
                    && clientException.getSubStatusCode() == HttpConstants.SubStatusCodes.PARTITION_KEY_RANGE_GONE);
        }
        return false;
    }

    protected boolean tryMoveNextProducer(DocumentProducer producer,
            Func1, DocumentProducer> producerRepairCallback) throws Exception {
        boolean movedNext = false;
        DocumentProducer currentProducer = producer;

        while (true) {
            boolean needRefreshedPartitionKeyRangeCache = false;
            try {
                movedNext = currentProducer.moveNext();
            } catch (Exception ex) {
                if (!(needRefreshedPartitionKeyRangeCache = this.needPartitionKeyRangeCacheRefresh(ex))) {
                    throw ex;
                } else {
                    LOGGER.debug("Encountered exception when moving to the next document producer", ex);
                }
            }

            if (needRefreshedPartitionKeyRangeCache) {
                currentProducer = producerRepairCallback.apply(currentProducer);
            } else {
                break;
            }
        }

        return movedNext;
    }

    private FeedOptions getFeedOptions(String continuationToken) {
        FeedOptions options = new FeedOptions((FeedOptions) this.options);
        options.setRequestContinuation(continuationToken);
        return options;
    }

    protected void repairContext(
            String collectionRid,
            int currentDocumentProducerIndex,
            Comparator> produceComparer,
            List replacementRanges,
            final SqlQuerySpec querySpecForRepair) {

        Map requestHeaders = this.getFeedHeaders(this.getFeedOptions(null));
        this.documentProducers.ensureCapacity(this.documentProducers.size() + replacementRanges.size() - 1);
        DocumentProducer replacedDocumentProducer = this.documentProducers.get(currentDocumentProducerIndex);

        int index = currentDocumentProducerIndex + 1;

        final ParallelDocumentQueryExecutionContextBase context = this;
        for (final PartitionKeyRange range : replacementRanges) {
            final Map documentProducerRequestHeader = new HashMap<>(requestHeaders);

            this.documentProducers.add(
                    index++,
                    new DocumentProducer(
                            this.executeFunc,
                            new Func2() {
                                @Override
                                public DocumentServiceRequest apply(String continuationToken, Integer pageSize) {
                                    DocumentServiceRequest request = context.createRequest(documentProducerRequestHeader, querySpecForRepair, range.getId());
                                    request.getHeaders().put(HttpConstants.HttpHeaders.PAGE_SIZE, Integer.toString(pageSize));
                                    request.getHeaders().put(HttpConstants.HttpHeaders.CONTINUATION, continuationToken);

                                    return request;
                                }
                            },
                            range,
                            documentProducerClassT,
                            this.fetchScheduler,
                            replacedDocumentProducer.getPageSize(),
                            replacedDocumentProducer.getCurrentBackendContinuationToken(), this.fetchCompletionCallback));
        }

        this.documentProducers.remove(currentDocumentProducerIndex);

        if (this.shouldPrefetch) {
            for (int i = 0; i < replacementRanges.size(); i++) {
                this.documentProducers.get(i + currentDocumentProducerIndex).tryScheduleFetch();
            }
        }
    }

    private void notifyStopDocumentProducers() {
        // Ensure producers are marked as not having next()
        for (int i = 0; i < this.documentProducers.size(); i++) {
            this.documentProducers.get(i).notifyStop();
        }
    }

    @Override
    public void onNotifyStop() {
        notifyStopDocumentProducers();
        try {
            this.fetchScheduler.stop();
            this.initializationFuture.get();
        } catch (Exception e) {
            LOGGER.warn("Failed to wait for Futures to finish.", e);
            throw toRuntimeException(unwrap(e));
        }

        this.onFinish();
    }

    protected void onFinish() {
        if (super.responseHeaders != null) {
            super.responseHeaders.put(HttpConstants.HttpHeaders.REQUEST_CHARGE,
                    String.valueOf(this.chargeTracker.getTotalRequestCharge()));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy