Example #1
0
        /// <summary>
        /// Gets the response headers for the context.
        /// </summary>
        /// <returns>The response headers for the context.</returns>
        public CosmosQueryResponseMessageHeaders GetResponseHeaders()
        {
            string continuationToken = this.ContinuationToken;

            if (continuationToken == "[]")
            {
                throw new InvalidOperationException("Somehow a document query execution context returned an empty array of continuations.");
            }

            CosmosQueryResponseMessageHeaders responseHeaders = new CosmosQueryResponseMessageHeaders(continuationToken, null, this.ResourceTypeEnum, this.ResourceLink);

            this.SetQueryMetrics();

            IReadOnlyDictionary <string, QueryMetrics> groupedQueryMetrics = this.GetQueryMetrics();

            if (groupedQueryMetrics != null && groupedQueryMetrics.Count != 0)
            {
                responseHeaders[HttpConstants.HttpHeaders.QueryMetrics] = QueryMetrics
                                                                          .CreateFromIEnumerable(groupedQueryMetrics.Values)
                                                                          .ToDelimitedString();
            }

            responseHeaders.RequestCharge = this.requestChargeTracker
                                            .GetAndResetCharge();

            return(responseHeaders);
        }
Example #2
0
        /// <summary>
        /// Function that is given to all the document producers to call on once they are done fetching.
        /// This is so that the CosmosCrossPartitionQueryExecutionContext can aggregate metadata from them.
        /// </summary>
        /// <param name="producer">The document producer that just finished fetching.</param>
        /// <param name="itemsBuffered">The number of items that the producer just fetched.</param>
        /// <param name="resourceUnitUsage">The amount of RUs that the producer just consumed.</param>
        /// <param name="queryMetrics">The query metrics that the producer just got back from the backend.</param>
        /// <param name="responseLengthBytes">The length of the response the producer just got back in bytes.</param>
        /// <param name="token">The cancellation token.</param>
        /// <remarks>
        /// This function is by nature a bit racy.
        /// A query might be fully drained but a background task is still fetching documents so this will get called after the context is done.
        /// </remarks>
        private void OnItemProducerTreeCompleteFetching(
            ItemProducerTree producer,
            int itemsBuffered,
            double resourceUnitUsage,
            QueryMetrics queryMetrics,
            long responseLengthBytes,
            CancellationToken token)
        {
            // Update charge and states
            this.requestChargeTracker.AddCharge(resourceUnitUsage);
            Interlocked.Add(ref this.totalBufferedItems, itemsBuffered);
            this.IncrementResponseLengthBytes(responseLengthBytes);
            this.partitionedQueryMetrics.Add(Tuple.Create(producer.PartitionKeyRange.Id, queryMetrics));

            // Adjust the producer page size so that we reach the optimal page size.
            producer.PageSize = Math.Min((long)(producer.PageSize * DynamicPageSizeAdjustmentFactor), this.actualMaxPageSize);

            // Adjust Max Degree Of Parallelism if necessary
            // (needs to wait for comparable task scheduler refactor).

            // Fetch again if necessary
            if (producer.HasMoreBackendResults)
            {
                // 4mb is the max response size
                long expectedResponseSize = Math.Min(producer.PageSize, 4 * 1024 * 1024);
                if (this.CanPrefetch && this.FreeItemSpace > expectedResponseSize)
                {
                    this.TryScheduleFetch(producer);
                }
            }
        }
Example #3
0
        /// <summary>
        /// Gets the response headers for the context.
        /// </summary>
        /// <returns>The response headers for the context.</returns>
        public INameValueCollection GetResponseHeaders()
        {
            StringKeyValueCollection responseHeaders = new StringKeyValueCollection();

            responseHeaders[HttpConstants.HttpHeaders.Continuation] = this.ContinuationToken;
            if (this.ContinuationToken == "[]")
            {
                throw new InvalidOperationException("Somehow a document query execution context returned an empty array of continuations.");
            }

            this.SetQueryMetrics();

            IReadOnlyDictionary <string, QueryMetrics> groupedQueryMetrics = this.GetQueryMetrics();

            if (groupedQueryMetrics != null && groupedQueryMetrics.Count != 0)
            {
                responseHeaders[HttpConstants.HttpHeaders.QueryMetrics] = QueryMetrics
                                                                          .CreateFromIEnumerable(groupedQueryMetrics.Values)
                                                                          .ToDelimitedString();
            }

            responseHeaders[HttpConstants.HttpHeaders.RequestCharge] = this.requestChargeTracker
                                                                       .GetAndResetCharge()
                                                                       .ToString(CultureInfo.InvariantCulture);

            return(responseHeaders);
        }
Example #4
0
        public void WriteQueryMetrics(QueryMetrics queryMetrics)
        {
            this.WriteBeforeQueryMetrics();

            // Top Level Properties
            this.WriteRetrievedDocumentCount(queryMetrics.RetrievedDocumentCount);
            this.WriteRetrievedDocumentSize(queryMetrics.RetrievedDocumentSize);
            this.WriteOutputDocumentCount(queryMetrics.OutputDocumentCount);
            this.WriteOutputDocumentSize(queryMetrics.OutputDocumentSize);
            this.WriteIndexHitRatio(queryMetrics.IndexHitRatio);

            this.WriteTotalQueryExecutionTime(queryMetrics.TotalQueryExecutionTime);

            // QueryPreparationTimes
            this.WriteQueryPreparationTimes(queryMetrics.QueryPreparationTimes);

            this.WriteIndexLookupTime(queryMetrics.IndexLookupTime);
            this.WriteDocumentLoadTime(queryMetrics.DocumentLoadTime);
            this.WriteVMExecutionTime(queryMetrics.VMExecutionTime);

            // RuntimesExecutionTimes
            this.WriteRuntimesExecutionTimes(queryMetrics.RuntimeExecutionTimes);

            this.WriteDocumentWriteTime(queryMetrics.DocumentWriteTime);

            // ClientSideMetrics
            this.WriteClientSideMetrics(queryMetrics.ClientSideMetrics);

            // IndexUtilizationInfo
            this.WriteBeforeIndexUtilizationInfo();

            this.WriteIndexUtilizationInfo(queryMetrics.IndexUtilizationInfo);

            this.WriteAfterQueryMetrics();
        }
Example #5
0
        /// <summary>
        /// Adds all QueryMetrics in a list along with the current instance.
        /// </summary>
        /// <param name="queryMetricsList">The list to sum up.</param>
        /// <returns>A new QueryMetrics instance that is the sum of the current instance and the list.</returns>
        internal QueryMetrics Add(params QueryMetrics[] queryMetricsList)
        {
            List <QueryMetrics> combinedQueryMetricsList = new List <QueryMetrics>(queryMetricsList.Length + 1)
            {
                this
            };

            combinedQueryMetricsList.AddRange(queryMetricsList);
            return(QueryMetrics.CreateFromIEnumerable(combinedQueryMetricsList));
        }
Example #6
0
        /// <summary>
        /// Function that is given to all the document producers to call on once they are done fetching.
        /// This is so that the CrossPartitionQueryExecutionContext can aggregate metadata from them.
        /// </summary>
        /// <param name="producer">The document producer that just finished fetching.</param>
        /// <param name="itemsBuffered">The number of items that the producer just fetched.</param>
        /// <param name="resourceUnitUsage">The amount of RUs that the producer just consumed.</param>
        /// <param name="queryMetrics">The query metrics that the producer just got back from the backend.</param>
        /// <param name="responseLengthBytes">The length of the response the producer just got back in bytes.</param>
        /// <param name="token">The cancellation token.</param>
        /// <remarks>
        /// This function is by nature a bit racy.
        /// A query might be fully drained but a background task is still fetching documents so this will get called after the context is done.
        /// </remarks>
        private void OnDocumentProducerTreeCompleteFetching(
            DocumentProducerTree producer,
            int itemsBuffered,
            double resourceUnitUsage,
            QueryMetrics queryMetrics,
            long responseLengthBytes,
            CancellationToken token)
        {
            // Update charge and states
            this.requestChargeTracker.AddCharge(resourceUnitUsage);
            Interlocked.Add(ref this.totalBufferedItems, itemsBuffered);
            this.IncrementResponseLengthBytes(responseLengthBytes);
            this.partitionedQueryMetrics.Add(Tuple.Create(producer.PartitionKeyRange.Id, queryMetrics));

            // Adjust the producer page size so that we reach the optimal page size.
            producer.PageSize = Math.Min((long)(producer.PageSize * DynamicPageSizeAdjustmentFactor), this.actualMaxPageSize);

            // Adjust Max Degree Of Paralleism if neccesary
            // (needs to wait for comparable task scheudler refactor).

            // Fetch again if necessary
            if (producer.HasMoreBackendResults)
            {
                // 4mb is the max reponse size
                long expectedResponseSize = Math.Min(producer.PageSize, 4 * 1024 * 1024);
                if (this.CanPrefetch && this.FreeItemSpace > expectedResponseSize)
                {
                    this.TryScheduleFetch(producer);
                }
            }

            this.TraceVerbose(string.Format(
                                  CultureInfo.InvariantCulture,
                                  "Id: {0}, size: {1}, resourceUnitUsage: {2}, taskScheduler.CurrentRunningTaskCount: {3}",
                                  producer.PartitionKeyRange.Id,
                                  itemsBuffered,
                                  resourceUnitUsage,
                                  this.comparableTaskScheduler.CurrentRunningTaskCount,
                                  this.CorrelatedActivityId));
        }
Example #7
0
 internal static QueryMetrics CreateWithSchedulingMetrics(
     QueryMetrics queryMetrics)
 {
     return(new QueryMetrics(
                queryMetrics.RetrievedDocumentCount,
                queryMetrics.RetrievedDocumentSize,
                queryMetrics.OutputDocumentCount,
                queryMetrics.OutputDocumentSize,
                queryMetrics.IndexHitDocumentCount,
                queryMetrics.IndexUtilizationInfo,
                queryMetrics.TotalQueryExecutionTime,
                queryMetrics.QueryPreparationTimes,
                queryMetrics.IndexLookupTime,
                queryMetrics.DocumentLoadTime,
                queryMetrics.VMExecutionTime,
                queryMetrics.RuntimeExecutionTimes,
                queryMetrics.DocumentWriteTime,
                new ClientSideMetrics(
                    queryMetrics.ClientSideMetrics.Retries,
                    queryMetrics.ClientSideMetrics.RequestCharge,
                    queryMetrics.ClientSideMetrics.FetchExecutionRanges)));
 }
        protected override async Task <DocumentFeedResponse <CosmosElement> > ExecuteInternalAsync(CancellationToken token)
        {
            CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

            PartitionKeyRangeCache partitionKeyRangeCache = await this.Client.GetPartitionKeyRangeCacheAsync();

            IDocumentClientRetryPolicy retryPolicyInstance = this.Client.ResetSessionTokenRetryPolicy.GetRequestPolicy();

            retryPolicyInstance = new InvalidPartitionExceptionRetryPolicy(retryPolicyInstance);
            if (base.ResourceTypeEnum.IsPartitioned())
            {
                retryPolicyInstance = new PartitionKeyRangeGoneRetryPolicy(
                    collectionCache,
                    partitionKeyRangeCache,
                    PathsHelper.GetCollectionPath(base.ResourceLink),
                    retryPolicyInstance);
            }

            return(await BackoffRetryUtility <DocumentFeedResponse <CosmosElement> > .ExecuteAsync(
                       async() =>
            {
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                ++this.retries;
                Tuple <DocumentFeedResponse <CosmosElement>, string> responseAndPartitionIdentifier = await this.ExecuteOnceAsync(retryPolicyInstance, token);
                DocumentFeedResponse <CosmosElement> response = responseAndPartitionIdentifier.Item1;
                string partitionIdentifier = responseAndPartitionIdentifier.Item2;
                if (!string.IsNullOrEmpty(response.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics]))
                {
                    this.fetchExecutionRangeAccumulator.EndFetchRange(
                        partitionIdentifier,
                        response.ActivityId,
                        response.Count,
                        this.retries);
                    response = new DocumentFeedResponse <CosmosElement>(
                        response,
                        response.Count,
                        response.Headers,
                        response.UseETagAsContinuation,
                        new Dictionary <string, QueryMetrics>
                    {
                        {
                            partitionIdentifier,
                            QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(
                                response.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics],
                                response.ResponseHeaders[HttpConstants.HttpHeaders.IndexUtilization],
                                new ClientSideMetrics(
                                    this.retries,
                                    response.RequestCharge,
                                    this.fetchExecutionRangeAccumulator.GetExecutionRanges()))
                        }
                    },
                        response.RequestStatistics,
                        response.DisallowContinuationTokenMessage,
                        response.ResponseLengthBytes);
                }

                this.retries = -1;
                return response;
            },
                       retryPolicyInstance,
                       token));
        }
Example #9
0
 /// <summary>
 /// Since query metrics are being aggregated asynchronously to the feed reponses as explained in the member documentation,
 /// this function allows us to take a snapshot of the query metrics.
 /// </summary>
 private void SetQueryMetrics()
 {
     this.groupedQueryMetrics = Interlocked.Exchange(ref this.partitionedQueryMetrics, new ConcurrentBag <Tuple <string, QueryMetrics> >())
                                .GroupBy(tuple => tuple.Item1, tuple => tuple.Item2)
                                .ToDictionary(group => group.Key, group => QueryMetrics.CreateFromIEnumerable(group));
 }
        /// <summary>
        /// Buffers more documents in the producer.
        /// </summary>
        /// <param name="token">The cancellation token.</param>
        /// <returns>A task to await on.</returns>
        public async Task BufferMoreDocumentsAsync(CancellationToken token)
        {
            token.ThrowIfCancellationRequested();

            try
            {
                await this.fetchSemaphore.WaitAsync();

                if (!this.HasMoreBackendResults)
                {
                    // Just NOP
                    return;
                }

                this.fetchSchedulingMetrics.Start();
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                int pageSize = (int)Math.Min(this.pageSize, (long)int.MaxValue);
                using (DocumentServiceRequest request = this.createRequestFunc(this.PartitionKeyRange, this.backendContinuationToken, pageSize))
                {
                    IDocumentClientRetryPolicy retryPolicy = this.createRetryPolicyFunc();

                    // Custom backoff and retry
                    while (true)
                    {
                        int retries = 0;
                        try
                        {
                            DocumentFeedResponse <CosmosElement> feedResponse = await this.executeRequestFunc(request, retryPolicy, token);

                            this.fetchExecutionRangeAccumulator.EndFetchRange(
                                this.PartitionKeyRange.Id,
                                feedResponse.ActivityId,
                                feedResponse.Count,
                                retries);
                            this.fetchSchedulingMetrics.Stop();
                            this.hasStartedFetching       = true;
                            this.backendContinuationToken = feedResponse.ResponseContinuation;
                            this.activityId = Guid.Parse(feedResponse.ActivityId);
                            await this.bufferedPages.AddAsync(TryMonad <DocumentFeedResponse <CosmosElement> > .FromResult(feedResponse));

                            Interlocked.Add(ref this.bufferedItemCount, feedResponse.Count);

                            QueryMetrics queryMetrics = QueryMetrics.Zero;
                            if (feedResponse.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics] != null)
                            {
                                queryMetrics = QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(
                                    feedResponse.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics],
                                    new ClientSideMetrics(
                                        retries,
                                        feedResponse.RequestCharge,
                                        this.fetchExecutionRangeAccumulator.GetExecutionRanges(),
                                        new List <Tuple <string, SchedulingTimeSpan> >()));
                            }

                            if (!this.HasMoreBackendResults)
                            {
                                queryMetrics = QueryMetrics.CreateWithSchedulingMetrics(
                                    queryMetrics,
                                    new List <Tuple <string, SchedulingTimeSpan> >
                                {
                                    new Tuple <string, SchedulingTimeSpan>(
                                        this.PartitionKeyRange.Id,
                                        this.fetchSchedulingMetrics.Elapsed)
                                });
                            }

                            this.produceAsyncCompleteCallback(
                                this,
                                feedResponse.Count,
                                feedResponse.RequestCharge,
                                queryMetrics,
                                feedResponse.ResponseLengthBytes,
                                token);

                            break;
                        }
                        catch (Exception exception)
                        {
                            // See if we need to retry or just throw
                            ShouldRetryResult shouldRetryResult = await retryPolicy.ShouldRetryAsync(exception, token);

                            if (!shouldRetryResult.ShouldRetry)
                            {
                                Exception exceptionToBuffer;
                                if (shouldRetryResult.ExceptionToThrow != null)
                                {
                                    exceptionToBuffer = shouldRetryResult.ExceptionToThrow;
                                }
                                else
                                {
                                    // Propagate original exception.
                                    exceptionToBuffer = exception;
                                }

                                // Buffer the exception instead of throwing, since we don't want an unobserved exception.
                                await this.bufferedPages.AddAsync(TryMonad <DocumentFeedResponse <CosmosElement> > .FromException(exceptionToBuffer));

                                // null out the backend continuation token,
                                // so that people stop trying to buffer more on this producer.
                                this.hasStartedFetching       = true;
                                this.backendContinuationToken = null;
                                break;
                            }
                            else
                            {
                                await Task.Delay(shouldRetryResult.BackoffTime);

                                retries++;
                            }
                        }
                    }
                }
            }
            finally
            {
                this.fetchSchedulingMetrics.Stop();
                this.fetchSemaphore.Release();
            }
        }
Example #11
0
        protected override async Task <FeedResponse <dynamic> > ExecuteInternalAsync(CancellationToken cancellationToken)
        {
            CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

            PartitionKeyRangeCache partitionKeyRangeCache = await this.Client.GetPartitionKeyRangeCache();

            IDocumentClientRetryPolicy retryPolicyInstance = this.Client.RetryPolicy.GetRequestPolicy();

            retryPolicyInstance = new InvalidPartitionExceptionRetryPolicy(collectionCache, retryPolicyInstance);
            if (base.ResourceTypeEnum.IsPartitioned())
            {
                retryPolicyInstance = new PartitionKeyRangeGoneRetryPolicy(
                    collectionCache,
                    partitionKeyRangeCache,
                    PathsHelper.GetCollectionPath(base.ResourceLink),
                    retryPolicyInstance);
            }

            return(await BackoffRetryUtility <FeedResponse <dynamic> > .ExecuteAsync(
                       async() =>
            {
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                ++this.retries;
                this.fetchSchedulingMetrics.Start();
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                FeedResponse <dynamic> response = await this.ExecuteOnceAsync(retryPolicyInstance, cancellationToken);
                this.fetchSchedulingMetrics.Stop();
                this.fetchExecutionRangeAccumulator.EndFetchRange(response.Count, this.retries);

                if (!string.IsNullOrEmpty(response.Headers[HttpConstants.HttpHeaders.QueryMetrics]))
                {
                    this.fetchExecutionRangeAccumulator.EndFetchRange(response.Count, this.retries);
                    response = new FeedResponse <dynamic>(
                        response,
                        response.Count,
                        response.Headers,
                        response.UseETagAsContinuation,
                        new Dictionary <string, QueryMetrics>
                    {
                        {
                            singlePartitionKeyId,
                            QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(
                                response.Headers[HttpConstants.HttpHeaders.QueryMetrics],
                                new ClientSideMetrics(
                                    this.retries,
                                    response.RequestCharge,
                                    this.fetchExecutionRangeAccumulator.GetExecutionRanges(),
                                    string.IsNullOrEmpty(response.ResponseContinuation) ? new List <Tuple <string, SchedulingTimeSpan> >()
                            {
                                new Tuple <string, SchedulingTimeSpan>(singlePartitionKeyId, this.fetchSchedulingMetrics.Elapsed)
                            } : new List <Tuple <string, SchedulingTimeSpan> >()),
                                Guid.Parse(response.ActivityId))
                        }
                    },
                        response.RequestStatistics,
                        response.DisallowContinuationTokenMessage,
                        response.ResponseLengthBytes);
                }

                this.retries = -1;
                return response;
            },
                       retryPolicyInstance,
                       cancellationToken));
        }
Example #12
0
 /// <summary>
 /// Creates a new QueryMetrics from the backend delimited string.
 /// </summary>
 /// <param name="delimitedString">The backend delimited string to deserialize from.</param>
 /// <param name="indexUtilization">The index utilization from the backend</param>
 /// <returns>A new QueryMetrics from the backend delimited string.</returns>
 internal static QueryMetrics CreateFromDelimitedString(string delimitedString, string indexUtilization)
 {
     return(QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(delimitedString, indexUtilization, new ClientSideMetrics(0, 0, new List <FetchExecutionRange>())));
 }
Example #13
0
        private void OnDocumentProducerCompleteFetching(
            DocumentProducer <T> producer,
            int size,
            double resourceUnitUsage,
            QueryMetrics queryMetrics,
            long responseLengthBytes,
            CancellationToken token)
        {
            // Update charge and states
            this.chargeTracker.AddCharge(resourceUnitUsage);
            Interlocked.Add(ref this.totalBufferedItems, size);
            Interlocked.Increment(ref this.totalRequestRoundTrips);
            this.IncrementResponseLengthBytes(responseLengthBytes);
            this.partitionedQueryMetrics.Add(Tuple.Create(producer.TargetRange.Id, queryMetrics));

            //Check to see if we can buffer more item
            long countToAdd = size - this.FreeItemSpace;

            if (countToAdd > 0 &&
                this.actualMaxBufferedItemCount < MaxixmumDynamicMaxBufferedItemCountValue - countToAdd)
            {
                DefaultTrace.TraceVerbose(string.Format(
                                              CultureInfo.InvariantCulture,
                                              "{0}, CorrelatedActivityId: {4} | Id: {1}, increasing MaxBufferedItemCount {2} by {3}.",
                                              DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture),
                                              producer.TargetRange.Id,
                                              this.actualMaxBufferedItemCount,
                                              countToAdd,
                                              this.CorrelatedActivityId));

                countToAdd += this.actualMaxBufferedItemCount;
            }

            // Adjust max DoP if necessary
            this.AdjustTaskSchedulerMaximumConcurrencyLevel();

            // Fetch again if necessary
            if (!producer.FetchedAll)
            {
                if (producer.PageSize < this.actualMaxPageSize)
                {
                    producer.PageSize = Math.Min((long)(producer.PageSize * DynamicPageSizeAdjustmentFactor), this.actualMaxPageSize);

                    Debug.Assert(producer.PageSize >= 0 && producer.PageSize <= int.MaxValue, string.Format("producer.PageSize is invalid at {0}", producer.PageSize));
                }

                if (this.ShouldPrefetch &&
                    this.FreeItemSpace - producer.NormalizedPageSize > 0)
                {
                    producer.TryScheduleFetch();
                }
            }

            DefaultTrace.TraceVerbose(string.Format(
                                          CultureInfo.InvariantCulture,
                                          "{0}, CorrelatedActivityId: {5} | Id: {1}, size: {2}, resourceUnitUsage: {3}, taskScheduler.CurrentRunningTaskCount: {4}",
                                          DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture),
                                          producer.TargetRange.Id,
                                          size,
                                          resourceUnitUsage,
                                          this.TaskScheduler.CurrentRunningTaskCount,
                                          this.CorrelatedActivityId));
        }