/// <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); }
/// <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); } } }
/// <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); }
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(); }
/// <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)); }
/// <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)); }
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)); }
/// <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(); } }
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)); }
/// <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>()))); }
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)); }