Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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()));
}
}
}