private async Task <long> GetRemainingWorkAsync(ILease existingLease) { ChangeFeedOptions options = new ChangeFeedOptions { MaxItemCount = 1, PartitionKeyRangeId = existingLease.PartitionId, RequestContinuation = existingLease.ContinuationToken, StartFromBeginning = string.IsNullOrEmpty(existingLease.ContinuationToken), }; IChangeFeedDocumentQuery <Document> query = this.feedDocumentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options); IFeedResponse <Document> response = null; try { response = await query.ExecuteNextAsync <Document>().ConfigureAwait(false); long parsedLSNFromSessionToken = TryConvertToNumber(ExtractLsnFromSessionToken(response.SessionToken)); long lastQueryLSN = response.Count > 0 ? TryConvertToNumber(GetFirstDocument(response).GetPropertyValue <string>(LSNPropertyName)) - 1 : parsedLSNFromSessionToken; if (lastQueryLSN < 0) { return(1); } long partitionRemainingWork = parsedLSNFromSessionToken - lastQueryLSN; return(partitionRemainingWork < 0 ? 0 : partitionRemainingWork); } catch (Exception clientException) { Logger.WarnException($"GetEstimateWork > exception: partition '{existingLease.PartitionId}'", clientException); throw; } }
/// <summary> /// Initializes a new instance of the <see cref="DocumentDB.ChangeFeedProcessor.ChangeFeedEventHost"/> class. /// </summary> /// <param name="hostName">Unique name for this host.</param> /// <param name="documentCollectionLocation">Specifies location of the DocumentDB collection to monitor changes for.</param> /// <param name="auxCollectionLocation">Specifies location of auxiliary data for load-balancing instances of <see cref="DocumentDB.ChangeFeedProcessor.ChangeFeedEventHost" />.</param> /// <param name="changeFeedOptions">Options to pass to the Microsoft.AzureDocuments.DocumentClient.CreateChangeFeedQuery API.</param> /// <param name="hostOptions">Additional options to control load-balancing of <see cref="DocumentDB.ChangeFeedProcessor.ChangeFeedEventHost" /> instances.</param> public ChangeFeedEventHost( string hostName, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo auxCollectionLocation, ChangeFeedOptions changeFeedOptions, ChangeFeedHostOptions hostOptions) { if (documentCollectionLocation == null) { throw new ArgumentException("documentCollectionLocation"); } if (documentCollectionLocation.Uri == null) { throw new ArgumentException("documentCollectionLocation.Uri"); } if (string.IsNullOrWhiteSpace(documentCollectionLocation.DatabaseName)) { throw new ArgumentException("documentCollectionLocation.DatabaseName"); } if (string.IsNullOrWhiteSpace(documentCollectionLocation.CollectionName)) { throw new ArgumentException("documentCollectionLocation.CollectionName"); } if (hostOptions.MinPartitionCount > hostOptions.MaxPartitionCount) { throw new ArgumentException("hostOptions.MinPartitionCount cannot be greater than hostOptions.MaxPartitionCount"); } this.collectionLocation = CanoninicalizeCollectionInfo(documentCollectionLocation); this.changeFeedOptions = changeFeedOptions; this.options = hostOptions; this.HostName = hostName; this.auxCollectionLocation = CanoninicalizeCollectionInfo(auxCollectionLocation); this.partitionKeyRangeIdToWorkerMap = new ConcurrentDictionary <string, WorkerData>(); }
public async Task StartAsync_Retries() { var mockExecutor = new Mock <ITriggeredFunctionExecutor>(); var collInfo = new DocumentCollectionInfo { Uri = new Uri("http://fakeaccount"), MasterKey = "c29tZV9rZXk=", DatabaseName = "FakeDb", CollectionName = "FakeColl" }; var leaseInfo = new DocumentCollectionInfo { Uri = new Uri("http://fakeaccount"), MasterKey = "c29tZV9rZXk=", DatabaseName = "FakeDb", CollectionName = "leases" }; var hostOptions = new ChangeFeedHostOptions(); var feedOptions = new ChangeFeedOptions(); var listener = new MockListener(mockExecutor.Object, collInfo, leaseInfo, hostOptions, feedOptions, NullLogger.Instance); // Ensure that we can call StartAsync() multiple times to retry if there is an error. for (int i = 0; i < 3; i++) { var ex = await Assert.ThrowsAnyAsync <InvalidOperationException>(() => listener.StartAsync(CancellationToken.None)); Assert.Equal("Failed to register!", ex.Message); } // This should succeed await listener.StartAsync(CancellationToken.None); }
public static async Task StartChangeFeedHost() { var config = ServiceLocator.GetService <IConfiguration>(); string hostName = config.MonitoredCollectionName + "_monitor_" + Guid.NewGuid().ToString(); DocumentCollectionInfo documentCollectionLocation = new DocumentCollectionInfo { Uri = config.DatabaseAccountUri, MasterKey = config.DatabaseAccountKey, DatabaseName = config.MonitoredDatabaseName, CollectionName = config.MonitoredCollectionName }; DocumentCollectionInfo leaseCollectionLocation = new DocumentCollectionInfo { Uri = config.DatabaseAccountUri, MasterKey = config.DatabaseAccountKey, DatabaseName = config.ChangeTrackingDatabaseName, CollectionName = config.ChangeTrackingLeaseCollectionName }; Console.WriteLine("Main program: Creating ChangeFeedEventHost..."); ChangeFeedOptions feedOptions = new ChangeFeedOptions(); ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions(); ChangeFeedEventHost host = new ChangeFeedEventHost(hostName, documentCollectionLocation, leaseCollectionLocation, feedOptions, feedHostOptions); await host.RegisterObserverAsync <DocumentFeedObserver>(); Console.WriteLine("Main program: press Enter to stop..."); Console.ReadLine(); await host.UnregisterObserversAsync(); }
public ChangeFeedQuery(DocumentClient client, ResourceType resourceType, string resourceLink, ChangeFeedOptions feedOptions) { Debug.Assert(client != null); this.client = client; this.resourceType = resourceType; this.resourceLink = resourceLink; this.feedOptions = feedOptions ?? new ChangeFeedOptions(); if (feedOptions.PartitionKey != null && !string.IsNullOrEmpty(feedOptions.PartitionKeyRangeId)) { throw new ArgumentException(RMResources.PartitionKeyAndPartitionKeyRangeRangeIdBothSpecified, "feedOptions"); } bool canUseStartFromBeginning = true; if (feedOptions.RequestContinuation != null) { this.nextIfNoneMatch = feedOptions.RequestContinuation; canUseStartFromBeginning = false; } if (feedOptions.StartTime.HasValue) { this.ifModifiedSince = this.ConvertToHttpTime(feedOptions.StartTime.Value); canUseStartFromBeginning = false; } if (canUseStartFromBeginning && !feedOptions.StartFromBeginning) { this.nextIfNoneMatch = IfNoneMatchAllHeaderValue; } }
/// <summary> /// Registers change feed observer to update changes read on change feed to destination /// collection. Deregisters change feed observer and closes process when enter key is pressed /// </summary> /// <returns>A Task to allow asynchronous execution</returns> public async Task RunChangeFeedHostAsync() { string hostName = Guid.NewGuid().ToString(); // monitored collection info DocumentCollectionInfo documentCollectionLocation = new DocumentCollectionInfo { Uri = new Uri(this.monitoredUri), MasterKey = this.monitoredSecretKey, DatabaseName = this.monitoredDbName, CollectionName = this.monitoredCollectionName }; // lease collection info DocumentCollectionInfo leaseCollectionLocation = new DocumentCollectionInfo { Uri = new Uri(this.leaseUri), MasterKey = this.leaseSecretKey, DatabaseName = this.leaseDbName, CollectionName = this.leaseCollectionName }; // destination collection info DocumentCollectionInfo destCollInfo = new DocumentCollectionInfo { Uri = new Uri(this.destUri), MasterKey = this.destSecretKey, DatabaseName = this.destDbName, CollectionName = this.destCollectionName }; // Customizable change feed option and host options ChangeFeedOptions feedOptions = new ChangeFeedOptions(); // ie customize StartFromBeginning so change feed reads from beginning // can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning feedOptions.StartFromBeginning = true; ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions(); // ie. customizing lease renewal interval to 15 seconds // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay feedHostOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15); using (DocumentClient destClient = new DocumentClient(destCollInfo.Uri, destCollInfo.MasterKey)) { DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(destClient, destCollInfo); ChangeFeedEventHost host = new ChangeFeedEventHost(hostName, documentCollectionLocation, leaseCollectionLocation, feedOptions, feedHostOptions); await host.RegisterObserverFactoryAsync(docObserverFactory); Console.WriteLine("Running... Press enter to stop."); Console.ReadLine(); await host.UnregisterObserversAsync(); } }
public PartitionProcessor(IChangeFeedObserver observer, IChangeFeedDocumentQuery <Document> query, ChangeFeedOptions options, ProcessorSettings settings, IPartitionCheckpointer checkpointer) { this.observer = observer; this.settings = settings; this.checkpointer = checkpointer; this.options = options; this.query = query; }
public CosmosDBTriggerBinding(ParameterInfo parameter, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedHostOptions leaseHostOptions, ChangeFeedOptions changeFeedOptions, ILogger logger) { _documentCollectionLocation = documentCollectionLocation; _leaseCollectionLocation = leaseCollectionLocation; _leaseHostOptions = leaseHostOptions; _parameter = parameter; _logger = logger; _changeFeedOptions = changeFeedOptions; }
public CosmosDBTriggerListener(ITriggeredFunctionExecutor executor, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedHostOptions leaseHostOptions, ChangeFeedOptions changeFeedOptions, ILogger logger) { this._logger = logger; this._executor = executor; this._hostName = Guid.NewGuid().ToString(); this._monitorCollection = documentCollectionLocation; this._leaseCollection = leaseCollectionLocation; this._leaseHostOptions = leaseHostOptions; this._changeFeedOptions = changeFeedOptions; }
/// <summary> /// Registers change feed observer to update changes read on change feed to destination /// collection. Deregisters change feed observer and closes process when enter key is pressed /// </summary> /// <returns>A Task to allow asynchronous execution</returns> public async Task RunChangeFeedHostAsync() { string hostName = Guid.NewGuid().ToString(); // monitored collection info DocumentCollectionInfo documentCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(this.monitoredUri), MasterKey = this.monitoredSecretKey, DatabaseName = this.monitoredDbName, CollectionName = this.monitoredCollectionName }; DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(this.leaseUri), MasterKey = this.leaseSecretKey, DatabaseName = this.leaseDbName, CollectionName = this.leaseCollectionName }; DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(); ChangeFeedOptions feedOptions = new ChangeFeedOptions(); /* ie customize StartFromBeginning so change feed reads from beginning * can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning */ feedOptions.StartFromBeginning = true; ChangeFeedProcessorOptions feedProcessorOptions = new ChangeFeedProcessorOptions(); // ie. customizing lease renewal interval to 15 seconds // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay feedProcessorOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15); ChangeFeedProcessorBuilder builder = new ChangeFeedProcessorBuilder(); builder .WithHostName(hostName) .WithFeedCollection(documentCollectionInfo) .WithLeaseCollection(leaseCollectionInfo) .WithProcessorOptions(feedProcessorOptions) .WithObserverFactory(new DocumentFeedObserverFactory()); // .WithObserver<DocumentFeedObserver>(); or just pass a observer var result = await builder.BuildAsync(); await result.StartAsync(); Console.Read(); await result.StopAsync(); }
/// <summary> /// Registers change feed observer to update changes read on change feed to destination /// collection. Deregisters change feed observer and closes process when enter key is pressed /// </summary> /// <returns>A Task to allow asynchronous execution</returns> public async Task RunChangeFeed() { string hostName = Guid.NewGuid().ToString(); // monitored collection info DocumentCollectionInfo starshipCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(ConfigurationManager.AppSettings["SqlApi.EndpointUrl"]), MasterKey = ConfigurationManager.AppSettings["SqlApi.AuthorizationKey"], DatabaseName = ConfigurationManager.AppSettings["SqlApi.DatabaseName"], CollectionName = ConfigurationManager.AppSettings["SqlApi.StarshipCollectionName"] }; // lease collection info DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(ConfigurationManager.AppSettings["SqlApi.EndpointUrl"]), MasterKey = ConfigurationManager.AppSettings["SqlApi.AuthorizationKey"], DatabaseName = ConfigurationManager.AppSettings["SqlApi.DatabaseName"], CollectionName = ConfigurationManager.AppSettings["SqlApi.LeaseCollectionName"] }; await CreateCollectionIfNotExistsAsync(starshipCollectionInfo); await CreateCollectionIfNotExistsAsync(leaseCollectionInfo); // Customizable change feed option and host options ChangeFeedOptions feedOptions = new ChangeFeedOptions(); // ie customize StartFromBeginning so change feed reads from beginning // can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning feedOptions.StartFromBeginning = true; ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions(); // ie. customizing lease renewal interval to 15 seconds // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay feedHostOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15); using (DocumentClient client = new DocumentClient(starshipCollectionInfo.Uri, starshipCollectionInfo.MasterKey)) { DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(client, starshipCollectionInfo); ChangeFeedEventHost host = new ChangeFeedEventHost(hostName, starshipCollectionInfo, leaseCollectionInfo, feedOptions, feedHostOptions); await host.RegisterObserverFactoryAsync(docObserverFactory).ConfigureAwait(false); Console.WriteLine("Running... Press enter to stop."); Console.ReadLine(); await host.UnregisterObserversAsync(); } }
public async Task <long> GetEstimatedRemainingWork() { long remainingWork = 0; bool hasAtLeastOneLease = false; ChangeFeedOptions options = new ChangeFeedOptions { MaxItemCount = 1, }; foreach (ILease existingLease in await this.leaseManager.ListAllLeasesAsync().ConfigureAwait(false)) { hasAtLeastOneLease = true; options.PartitionKeyRangeId = existingLease.PartitionId; options.RequestContinuation = existingLease.ContinuationToken; options.StartFromBeginning = string.IsNullOrEmpty(existingLease.ContinuationToken); IChangeFeedDocumentQuery <Document> query = this.feedDocumentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options); IFeedResponse <Document> response = null; try { response = await query.ExecuteNextAsync <Document>().ConfigureAwait(false); long parsedLSNFromSessionToken = TryConvertToNumber(ExtractLSNFromSessionToken(response.SessionToken)); long lastQueryLSN = response.Count > 0 ? TryConvertToNumber(GetFirstDocument(response).GetPropertyValue <string>(LSNPropertyName)) - 1 : parsedLSNFromSessionToken; if (lastQueryLSN < 0) { // Could not parse LSN from document, we cannot determine the amount of changes but since the query returned 1 document, we know it's at least 1 remainingWork += 1; continue; } long partitionRemainingWork = parsedLSNFromSessionToken - lastQueryLSN; remainingWork += partitionRemainingWork < 0 ? 0 : partitionRemainingWork; } catch (DocumentClientException clientException) { Logger.WarnException("GetEstimateWork > exception: partition '{0}'", clientException, existingLease.PartitionId); } } if (!hasAtLeastOneLease) { return(1); } return(remainingWork); }
/// <summary> /// Asynchronously checks the current existing leases and calculates an estimate of remaining work per leased partitions. /// </summary> /// <returns>An estimate amount of remaining documents to be processed</returns> public async Task <long> GetEstimatedRemainingWork() { await this.InitializeAsync(); long remaining = 0; ChangeFeedOptions options = new ChangeFeedOptions { MaxItemCount = 1 }; foreach (DocumentServiceLease existingLease in await this.leaseManager.ListLeases()) { options.PartitionKeyRangeId = existingLease.PartitionId; options.RequestContinuation = existingLease.ContinuationToken; IDocumentQuery <Document> query = this.documentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options); FeedResponse <Document> response = null; try { response = await query.ExecuteNextAsync <Document>(); long parsedLSNFromSessionToken = TryConvertToNumber(ParseAmountFromSessionToken(response.SessionToken)); long lastSequenceNumber = response.Count > 0 ? TryConvertToNumber(response.First().GetPropertyValue <string>(LSNPropertyName)) : parsedLSNFromSessionToken; long partitionRemaining = parsedLSNFromSessionToken - lastSequenceNumber; remaining += partitionRemaining < 0 ? 0 : partitionRemaining; } catch (DocumentClientException ex) { ExceptionDispatchInfo exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); DocumentClientException dcex = (DocumentClientException)exceptionDispatchInfo.SourceException; if ((StatusCode.NotFound == (StatusCode)dcex.StatusCode && SubStatusCode.ReadSessionNotAvailable != (SubStatusCode)GetSubStatusCode(dcex)) || StatusCode.Gone == (StatusCode)dcex.StatusCode) { // We are not explicitly handling Splits here to avoid any collision with an Observer that might have picked this up and managing the split TraceLog.Error(string.Format("GetEstimateWork > Partition {0}: resource gone (subStatus={1}).", existingLease.PartitionId, GetSubStatusCode(dcex))); } else if (StatusCode.TooManyRequests == (StatusCode)dcex.StatusCode || StatusCode.ServiceUnavailable == (StatusCode)dcex.StatusCode) { TraceLog.Warning(string.Format("GetEstimateWork > Partition {0}: retriable exception : {1}", existingLease.PartitionId, dcex.Message)); } else { TraceLog.Error(string.Format("GetEstimateWork > Partition {0}: Unhandled exception", ex.Error.Message)); } } } return(remaining); }
private static async Task RunChangeFeedHostAsync() { // monitored collection info DocumentCollectionInfo documentCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(cosmosSettings.DbUrl), MasterKey = cosmosSettings.AuthorizationKey, DatabaseName = cosmosSettings.DbName, CollectionName = cosmosSettings.CollectionName }; DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(cosmosSettings.DbUrl), MasterKey = cosmosSettings.AuthorizationKey, DatabaseName = cosmosSettings.DbName, CollectionName = cosmosSettings.LeaseCollectionName }; DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(); ChangeFeedOptions feedOptions = new ChangeFeedOptions { /* ie customize StartFromBeginning so change feed reads from beginning * can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning */ StartFromBeginning = true }; ChangeFeedProcessorOptions feedProcessorOptions = new ChangeFeedProcessorOptions(); // ie. customizing lease renewal interval to 15 seconds // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay feedProcessorOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15); ChangeFeedProcessorBuilder builder = new ChangeFeedProcessorBuilder(); builder .WithHostName(hostName) .WithFeedCollection(documentCollectionInfo) .WithLeaseCollection(leaseCollectionInfo) .WithProcessorOptions(feedProcessorOptions) .WithObserverFactory(new DocumentFeedObserverFactory()); //.WithObserver<DocumentFeedObserver>(); If no factory then just pass an observer var result = await builder.BuildAsync(); await result.StartAsync(); Console.Read(); await result.StopAsync(); }
private async void OnStarted() // private void OnStarted() { _logger.LogInformation("OnStarted has been called."); // Perform post-startup activities here _logger.LogInformation("Log directory is " + localLogdirectory); string hostName = Guid.NewGuid().ToString(); DocumentCollectionInfo documentCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(_configuration["az_cosmos_uri"]), MasterKey = _configuration["az_cosmos_key"], DatabaseName = _configuration["az_cosmos_db_name"], CollectionName = _configuration["az_cosmos_collection_name"] }; DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo { Uri = new Uri(_configuration["az_cosmos_uri"]), MasterKey = _configuration["az_cosmos_key"], DatabaseName = _configuration["az_cosmos_db_name"], CollectionName = _configuration["az_cosmos_lease_collection_name"] }; DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(); ChangeFeedOptions feedOptions = new ChangeFeedOptions(); feedOptions.StartFromBeginning = true; ChangeFeedProcessorOptions feedProcessorOptions = new ChangeFeedProcessorOptions(); feedProcessorOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15); this.builder .WithHostName(hostName) .WithFeedCollection(documentCollectionInfo) .WithLeaseCollection(leaseCollectionInfo) .WithProcessorOptions(feedProcessorOptions) .WithObserverFactory(new DocumentFeedObserverFactory()); //; // .WithObserver<DocumentFeedObserver>(); //or just pass a observer iChangeFeedProcessor = await this.builder.BuildAsync(); await iChangeFeedProcessor.StartAsync(); }
public IPartitionProcessor Create(ILease lease, ILeaseCheckpointer leaseCheckpointer, IChangeFeedObserver observer) { if (observer == null) { throw new ArgumentNullException(nameof(observer)); } if (lease == null) { throw new ArgumentNullException(nameof(lease)); } if (leaseCheckpointer == null) { throw new ArgumentNullException(nameof(leaseCheckpointer)); } var settings = new ProcessorSettings { CollectionSelfLink = this.collectionSelfLink, StartContinuation = !string.IsNullOrEmpty(lease.ContinuationToken) ? lease.ContinuationToken : this.changeFeedProcessorOptions.StartContinuation, PartitionKeyRangeId = lease.PartitionId, FeedPollDelay = this.changeFeedProcessorOptions.FeedPollDelay, MaxItemCount = this.changeFeedProcessorOptions.MaxItemCount, StartFromBeginning = this.changeFeedProcessorOptions.StartFromBeginning, StartTime = this.changeFeedProcessorOptions.StartTime, SessionToken = this.changeFeedProcessorOptions.SessionToken, }; var checkpointer = new PartitionCheckpointer(leaseCheckpointer, lease); var changeFeedOptions = new ChangeFeedOptions { MaxItemCount = settings.MaxItemCount, PartitionKeyRangeId = settings.PartitionKeyRangeId, SessionToken = settings.SessionToken, StartFromBeginning = settings.StartFromBeginning, RequestContinuation = settings.StartContinuation, StartTime = settings.StartTime, }; var changeFeedQuery = this.documentClient.CreateDocumentChangeFeedQuery(settings.CollectionSelfLink, changeFeedOptions); changeFeedQuery = new ChangeFeedQueryTimeoutDecorator(changeFeedQuery, this.healthMonitor, this.changeFeedProcessorOptions.ChangeFeedTimeout, lease); return(new PartitionProcessor(observer, changeFeedQuery, changeFeedOptions, settings, checkpointer)); }
public CosmosDBTriggerListener(ITriggeredFunctionExecutor executor, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedHostOptions leaseHostOptions, int?maxItemCount) { this.executor = executor; string hostName = Guid.NewGuid().ToString(); monitorCollection = documentCollectionLocation; leaseCollection = leaseCollectionLocation; ChangeFeedOptions changeFeedOptions = new ChangeFeedOptions(); if (maxItemCount.HasValue) { changeFeedOptions.MaxItemCount = maxItemCount; } this.host = new ChangeFeedEventHost(hostName, documentCollectionLocation, leaseCollectionLocation, changeFeedOptions, leaseHostOptions); }
public PartitionProcessor(IChangeFeedObserver observer, IChangeFeedDocumentClient documentClient, ProcessorSettings settings, IPartitionCheckpointer checkpointer) { this.observer = observer; this.settings = settings; this.checkpointer = checkpointer; this.options = new ChangeFeedOptions { MaxItemCount = settings.MaxItemCount, PartitionKeyRangeId = settings.PartitionKeyRangeId, SessionToken = settings.SessionToken, StartFromBeginning = settings.StartFromBeginning, RequestContinuation = settings.RequestContinuation, StartTime = settings.StartTime, }; this.query = documentClient.CreateDocumentChangeFeedQuery(settings.CollectionSelfLink, this.options); }
public FeedProcessorCore(ChangeFeedObserver <T> observer, CosmosContainer container, ProcessorSettings settings, PartitionCheckpointer checkpointer) { this.observer = observer; this.settings = settings; this.checkpointer = checkpointer; this.options = new ChangeFeedOptions { MaxItemCount = settings.MaxItemCount, PartitionKeyRangeId = settings.LeaseToken, SessionToken = settings.SessionToken, StartFromBeginning = settings.StartFromBeginning, RequestContinuation = settings.StartContinuation, StartTime = settings.StartTime, }; this.query = container.Client.DocumentClient.CreateDocumentChangeFeedQuery(container.LinkUri.ToString(), this.options); }
public PartitionExceptionsTests() { processorSettings = new ProcessorSettings { CollectionSelfLink = "selfLink", FeedPollDelay = TimeSpan.FromMilliseconds(16), MaxItemCount = 5, PartitionKeyRangeId = "keyRangeId", StartContinuation = "initialToken" }; var document = new Document(); documents = new List <Document> { document }; feedResponse = Mock.Of <IFeedResponse <Document> >(); Mock.Get(feedResponse) .Setup(response => response.Count) .Returns(documents.Count); Mock.Get(feedResponse) .Setup(response => response.ResponseContinuation) .Returns("token"); Mock.Get(feedResponse) .Setup(response => response.GetEnumerator()) .Returns(documents.GetEnumerator()); documentQuery = Mock.Of <IChangeFeedDocumentQuery <Document> >(); Mock.Get(documentQuery) .Setup(query => query.HasMoreResults) .Returns(false); var options = new ChangeFeedOptions(); observer = Mock.Of <IChangeFeedObserver>(); Mock.Get(observer) .Setup(feedObserver => feedObserver .ProcessChangesAsync(It.IsAny <ChangeFeedObserverContext>(), It.IsAny <IReadOnlyList <Document> >(), It.IsAny <CancellationToken>())) .Returns(Task.CompletedTask) .Callback(cancellationTokenSource.Cancel); var checkPointer = new Mock <IPartitionCheckpointer>(); partitionProcessor = new PartitionProcessor(observer, documentQuery, options, processorSettings, checkPointer.Object); }
public void ValidateLegacyOptionsAreUsed() { DateTime startTime = DateTime.Now; var feedOptions = new ChangeFeedOptions { MaxItemCount = 1, StartFromBeginning = true, StartTime = startTime, RequestContinuation = "RequestContinuation", SessionToken = "SessionToken", }; var checkpointFrequency = new CheckpointFrequency { ExplicitCheckpoint = true }; var hostOptions = new ChangeFeedHostOptions { LeaseRenewInterval = TimeSpan.FromSeconds(2), LeaseAcquireInterval = TimeSpan.FromSeconds(3), LeaseExpirationInterval = TimeSpan.FromSeconds(4), FeedPollDelay = TimeSpan.FromSeconds(5), CheckpointFrequency = checkpointFrequency, LeasePrefix = "LeasePrefix", MinPartitionCount = 6, MaxPartitionCount = 7, QueryPartitionsMaxBatchSize = 8, }; var processorOptions = ChangeFeedEventHost.CreateProcessorOptions(feedOptions, hostOptions); Assert.Equal(1, processorOptions.MaxItemCount); Assert.True(processorOptions.StartFromBeginning); Assert.Equal(startTime, processorOptions.StartTime); Assert.Equal("RequestContinuation", processorOptions.RequestContinuation); Assert.Equal("SessionToken", processorOptions.SessionToken); Assert.Equal(TimeSpan.FromSeconds(2), processorOptions.LeaseRenewInterval); Assert.Equal(TimeSpan.FromSeconds(3), processorOptions.LeaseAcquireInterval); Assert.Equal(TimeSpan.FromSeconds(4), processorOptions.LeaseExpirationInterval); Assert.Equal(TimeSpan.FromSeconds(5), processorOptions.FeedPollDelay); Assert.Equal(checkpointFrequency, processorOptions.CheckpointFrequency); Assert.Equal("LeasePrefix", processorOptions.LeasePrefix); Assert.Equal(6, processorOptions.MinPartitionCount); Assert.Equal(7, processorOptions.MaxPartitionCount); Assert.Equal(8, processorOptions.QueryPartitionsMaxBatchSize); }
static async Task<Tuple<long, double>> ProcessPartitionKeyRangeId(Uri sourceCollectionUri, PartitionKeyRange pkRange, string continuation) { long totalCount = 0; double totalRequestCharge = 0; List<Task> tasks = new List<Task>(); ChangeFeedOptions options = new ChangeFeedOptions { PartitionKeyRangeId = pkRange.Id, StartFromBeginning = true, RequestContinuation = continuation, //MaxItemCount = -1 MaxItemCount = 1000 }; using (var query = sourceClient.CreateDocumentChangeFeedQuery(sourceCollectionUri, options)) { do { var readChangesResponse = await query.ExecuteNextAsync<Document>(); totalCount += readChangesResponse.Count; totalRequestCharge += readChangesResponse.RequestCharge; Console.WriteLine("Count of documents in this page : {0}", readChangesResponse.Count); Debug.WriteLine("Count of documents in this page : {0}", readChangesResponse.Count); //Console.WriteLine("Request charge for these documents : {0}", readChangesResponse.RequestCharge); if (readChangesResponse.Count > 0) { foreach (Document changedDocument in readChangesResponse) { tasks.Add(targetClient.UpsertDocumentAsync(sourceCollectionUri, changedDocument)); } await Task.WhenAll(tasks); checkpoints[pkRange.Id] = readChangesResponse.ResponseContinuation; } } while (query.HasMoreResults); } return Tuple.Create(totalCount, totalRequestCharge); }
internal static ChangeFeedProcessorOptions CreateProcessorOptions(ChangeFeedOptions feedOptions, ChangeFeedHostOptions hostOptions) { Debug.Assert(feedOptions != null, nameof(feedOptions)); Debug.Assert(hostOptions != null, nameof(hostOptions)); if (!string.IsNullOrEmpty(feedOptions.PartitionKeyRangeId)) { throw new ArgumentException("changeFeedOptions.PartitionKeyRangeId must be null or empty string.", nameof(feedOptions.PartitionKeyRangeId)); } if (feedOptions.PartitionKey != null) { throw new ArgumentException("changeFeedOptions.PartitionKey must be null.", nameof(feedOptions.PartitionKey)); } if (hostOptions.DiscardExistingLeases) { throw new ArgumentException("hostOptions.DiscardExistingLeases is no longer supported.", nameof(hostOptions.DiscardExistingLeases)); } return(new ChangeFeedProcessorOptions { MaxItemCount = feedOptions.MaxItemCount, StartFromBeginning = feedOptions.StartFromBeginning, StartTime = feedOptions.StartTime, RequestContinuation = feedOptions.RequestContinuation, SessionToken = feedOptions.SessionToken, LeaseRenewInterval = hostOptions.LeaseRenewInterval, LeaseAcquireInterval = hostOptions.LeaseAcquireInterval, LeaseExpirationInterval = hostOptions.LeaseExpirationInterval, FeedPollDelay = hostOptions.FeedPollDelay, CheckpointFrequency = hostOptions.CheckpointFrequency, LeasePrefix = hostOptions.LeasePrefix, MinPartitionCount = hostOptions.MinPartitionCount, MaxPartitionCount = hostOptions.MaxPartitionCount, DiscardExistingLeases = hostOptions.DiscardExistingLeases, QueryPartitionsMaxBatchSize = hostOptions.QueryPartitionsMaxBatchSize, }); }
public async Task <Dictionary <string, int> > GetTopPartitionKeys(Connection connection, DocumentCollection collection, string partitionKeyRangeId, int sampleCount = 100) { var stats = new Dictionary <string, int>(); var partitionKey = collection.PartitionKey.Paths[0].TrimStart('/'); var readCount = 0; var client = GetClient(connection); var options = new ChangeFeedOptions { StartFromBeginning = true, MaxItemCount = -1, PartitionKeyRangeId = partitionKeyRangeId }; while (readCount < sampleCount) { var results = await client.CreateDocumentChangeFeedQuery(collection.AltLink, options) .ExecuteNextAsync <Document>().ConfigureAwait(false); if (results.Count == 0) { break; } foreach (var document in results) { var key = document.GetPropertyValue <string>(partitionKey) ?? "N/A"; if (stats.ContainsKey(key)) { stats[key]++; } else { stats.Add(key, 1); } } readCount += results.Count; } return(stats); }
private async Task StartChangeFeedHost() { ConnectionPolicy ConnectionPolicy = new ConnectionPolicy { ConnectionMode = ConnectionMode.Direct, ConnectionProtocol = Protocol.Tcp, RequestTimeout = new TimeSpan(1, 0, 0), MaxConnectionLimit = 1000, RetryOptions = new RetryOptions { MaxRetryAttemptsOnThrottledRequests = 10, MaxRetryWaitTimeInSeconds = 60 } }; //Customizable change feed option and host options ChangeFeedOptions feedOptions = new ChangeFeedOptions(); // ie customize StartFromBeginning so change feed reads from beginning // can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning feedOptions.StartTime = this.Config.ChangeFeedStartTime; var feedHostOptions = new ChangeFeedHostOptions { // ie. customizing lease renewal interval to 15 seconds // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay LeaseRenewInterval = TimeSpan.FromSeconds(15) }; string hostName = Guid.NewGuid().ToString(); DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(this.Config); this.ChangeFeedEventHost = new ChangeFeedEventHost(hostName, Config.SourceDocumentCollectionInfo, this.Config.LeaseDocumentCollectionInfo, feedOptions, feedHostOptions); await this.ChangeFeedEventHost.RegisterObserverFactoryAsync(docObserverFactory); Console.WriteLine("Running... Press enter to stop."); }
private async Task <string> ReadStreamAsync(string partitionKeyRangeId, string continuation, DataWriter sw) { var feedOptions = new ChangeFeedOptions { PartitionKeyRangeId = partitionKeyRangeId, RequestContinuation = continuation, StartFromBeginning = options.StartFromBeginning }; var count = 0; using (var query = client.CreateDocumentChangeFeedQuery(collection.SelfLink, feedOptions)) { do { var response = await query.ExecuteNextAsync().ConfigureAwait(false); if (response.Count > 0) { foreach (var item in response) { await sw.WriteAsync(JToken.FromObject(item)).ConfigureAwait(false); count++; } } continuation = response.ResponseContinuation; }while (query.HasMoreResults); } if (count > 0) { Log.Information("Wrote {Count} changes to {Collection}", count, collection.Id); } return(continuation); }
private static async Task Run() { ChangeFeedOptions feedOptions = new ChangeFeedOptions { StartFromBeginning = true }; // Can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions { LeaseRenewInterval = TimeSpan.FromSeconds(15) }; DocumentCollectionInfo documentCollectionLocation = new DocumentCollectionInfo { Uri = new Uri(Connection.EndpointUrl), CollectionName = "FamilyCollection", // ConnectionPolicy = new ConnectionPolicy{}, DatabaseName = "FamilyDB", MasterKey = Connection.PrimaryKey }; ChangeFeedEventHost changeFeedEventHost = new ChangeFeedEventHost("hostName", documentCollectionLocation, documentCollectionLocation, feedOptions, feedHostOptions); ChangeFeedObserverFactory changeFeedObserverFactory = new ChangeFeedObserverFactory(); await changeFeedEventHost.RegisterObserverFactoryAsync(changeFeedObserverFactory); await changeFeedEventHost.UnregisterObserversAsync(); }
/// <summary> /// Initializes a new instance of the <see cref="ChangeFeedEventHost"/> class. /// </summary> /// <param name="hostName">Unique name for this host.</param> /// <param name="feedCollectionLocation">Specifies location of the Cosmos DB collection to monitor changes for.</param> /// <param name="leaseCollectionLocation">Specifies location of auxiliary data for load-balancing instances of <see cref="ChangeFeedEventHost" />.</param> /// <param name="changeFeedOptions">Options to pass to the DocumentClient.CreateDocumentChangeFeedQuery API.</param> /// <param name="changeFeedHostOptions">Additional options to control load-balancing of <see cref="ChangeFeedEventHost" /> instances.</param> public ChangeFeedEventHost( string hostName, DocumentCollectionInfo feedCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedOptions changeFeedOptions, ChangeFeedHostOptions changeFeedHostOptions) { if (string.IsNullOrEmpty(hostName)) { throw new ArgumentNullException(nameof(hostName)); } if (feedCollectionLocation == null) { throw new ArgumentNullException(nameof(feedCollectionLocation)); } if (leaseCollectionLocation == null) { throw new ArgumentNullException(nameof(leaseCollectionLocation)); } if (changeFeedOptions == null) { throw new ArgumentNullException(nameof(changeFeedOptions)); } if (changeFeedHostOptions == null) { throw new ArgumentNullException(nameof(changeFeedHostOptions)); } ChangeFeedEventHost.TraceLogProvider.OpenNestedContext(hostName); this.builder .WithHostName(hostName) .WithFeedCollection(feedCollectionLocation) .WithProcessorOptions(ChangeFeedEventHost.CreateProcessorOptions(changeFeedOptions, changeFeedHostOptions)) .WithLeaseCollection(leaseCollectionLocation); }
async Task IPartitionObserver <DocumentServiceLease> .OnPartitionAcquiredAsync(DocumentServiceLease lease) { Debug.Assert(lease != null && !string.IsNullOrEmpty(lease.Owner), "lease"); TraceLog.Informational(string.Format("Host '{0}' partition {1}: acquired!", this.HostName, lease.PartitionId)); #if DEBUG Interlocked.Increment(ref this.partitionCount); #endif IChangeFeedObserver observer = this.observerFactory.CreateObserver(); ChangeFeedObserverContext context = new ChangeFeedObserverContext { PartitionKeyRangeId = lease.PartitionId }; CancellationTokenSource cancellation = new CancellationTokenSource(); // Create ChangeFeedOptions to use for this worker. ChangeFeedOptions options = new ChangeFeedOptions { MaxItemCount = this.changeFeedOptions.MaxItemCount, PartitionKeyRangeId = this.changeFeedOptions.PartitionKeyRangeId, SessionToken = this.changeFeedOptions.SessionToken, StartFromBeginning = this.changeFeedOptions.StartFromBeginning, RequestContinuation = this.changeFeedOptions.RequestContinuation }; var workerTask = await Task.Factory.StartNew(async() => { ChangeFeedObserverCloseReason?closeReason = null; try { try { await observer.OpenAsync(context); } catch (Exception ex) { TraceLog.Error(string.Format("IChangeFeedObserver.OpenAsync exception: {0}", ex)); closeReason = ChangeFeedObserverCloseReason.ObserverError; throw; } options.PartitionKeyRangeId = lease.PartitionId; if (!string.IsNullOrEmpty(lease.ContinuationToken)) { options.RequestContinuation = lease.ContinuationToken; } CheckpointStats checkpointStats = null; if (!this.statsSinceLastCheckpoint.TryGetValue(lease.PartitionId, out checkpointStats) || checkpointStats == null) { // It could be that the lease was created by different host and we picked it up. checkpointStats = this.statsSinceLastCheckpoint.AddOrUpdate( lease.PartitionId, new CheckpointStats(), (partitionId, existingStats) => existingStats); Trace.TraceWarning(string.Format("Added stats for partition '{0}' for which the lease was picked up after the host was started.", lease.PartitionId)); } IDocumentQuery <Document> query = this.documentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options); TraceLog.Verbose(string.Format("Worker start: partition '{0}', continuation '{1}'", lease.PartitionId, lease.ContinuationToken)); string lastContinuation = options.RequestContinuation; try { while (this.isShutdown == 0) { do { ExceptionDispatchInfo exceptionDispatchInfo = null; FeedResponse <Document> response = null; try { response = await query.ExecuteNextAsync <Document>(); lastContinuation = response.ResponseContinuation; } catch (DocumentClientException ex) { exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); } if (exceptionDispatchInfo != null) { DocumentClientException dcex = (DocumentClientException)exceptionDispatchInfo.SourceException; if (StatusCode.NotFound == (StatusCode)dcex.StatusCode && SubStatusCode.ReadSessionNotAvailable != (SubStatusCode)GetSubStatusCode(dcex)) { // Most likely, the database or collection was removed while we were enumerating. // Shut down. The user will need to start over. // Note: this has to be a new task, can't await for shutdown here, as shudown awaits for all worker tasks. TraceLog.Error(string.Format("Partition {0}: resource gone (subStatus={1}). Aborting.", context.PartitionKeyRangeId, GetSubStatusCode(dcex))); await Task.Factory.StartNew(() => this.StopAsync(ChangeFeedObserverCloseReason.ResourceGone)); break; } else if (StatusCode.Gone == (StatusCode)dcex.StatusCode) { SubStatusCode subStatusCode = (SubStatusCode)GetSubStatusCode(dcex); if (SubStatusCode.PartitionKeyRangeGone == subStatusCode) { bool isSuccess = await HandleSplitAsync(context.PartitionKeyRangeId, lastContinuation, lease.Id); if (!isSuccess) { TraceLog.Error(string.Format("Partition {0}: HandleSplit failed! Aborting.", context.PartitionKeyRangeId)); await Task.Factory.StartNew(() => this.StopAsync(ChangeFeedObserverCloseReason.ResourceGone)); break; } // Throw LeaseLostException so that we take the lease down. throw new LeaseLostException(lease, exceptionDispatchInfo.SourceException, true); } else if (SubStatusCode.Splitting == subStatusCode) { TraceLog.Warning(string.Format("Partition {0} is splitting. Will retry to read changes until split finishes. {1}", context.PartitionKeyRangeId, dcex.Message)); } else { exceptionDispatchInfo.Throw(); } } else if (StatusCode.TooManyRequests == (StatusCode)dcex.StatusCode || StatusCode.ServiceUnavailable == (StatusCode)dcex.StatusCode) { TraceLog.Warning(string.Format("Partition {0}: retriable exception : {1}", context.PartitionKeyRangeId, dcex.Message)); } else { exceptionDispatchInfo.Throw(); } await Task.Delay(dcex.RetryAfter != TimeSpan.Zero ? dcex.RetryAfter : this.options.FeedPollDelay, cancellation.Token); } if (response != null) { if (response.Count > 0) { List <Document> docs = new List <Document>(); docs.AddRange(response); try { context.FeedResponse = response; await observer.ProcessChangesAsync(context, docs); } catch (Exception ex) { TraceLog.Error(string.Format("IChangeFeedObserver.ProcessChangesAsync exception: {0}", ex)); closeReason = ChangeFeedObserverCloseReason.ObserverError; throw; } finally { context.FeedResponse = null; } } checkpointStats.ProcessedDocCount += (uint)response.Count; if (IsCheckpointNeeded(lease, checkpointStats)) { lease = await CheckpointAsync(lease, response.ResponseContinuation, context); checkpointStats.Reset(); } else if (response.Count > 0) { TraceLog.Informational(string.Format("Checkpoint: not checkpointing for partition {0}, {1} docs, new continuation '{2}' as frequency condition is not met", lease.PartitionId, response.Count, response.ResponseContinuation)); } } }while (query.HasMoreResults && this.isShutdown == 0); if (this.isShutdown == 0) { await Task.Delay(this.options.FeedPollDelay, cancellation.Token); } } // Outer while (this.isShutdown == 0) loop. closeReason = ChangeFeedObserverCloseReason.Shutdown; } catch (TaskCanceledException) { Debug.Assert(cancellation.IsCancellationRequested, "cancellation.IsCancellationRequested"); TraceLog.Informational(string.Format("Cancel signal received for partition {0} worker!", context.PartitionKeyRangeId)); } } catch (LeaseLostException ex) { closeReason = ex.IsGone ? ChangeFeedObserverCloseReason.LeaseGone : ChangeFeedObserverCloseReason.LeaseLost; } catch (Exception ex) { TraceLog.Error(string.Format("Partition {0} exception: {1}", context.PartitionKeyRangeId, ex)); if (!closeReason.HasValue) { closeReason = ChangeFeedObserverCloseReason.Unknown; } } if (closeReason.HasValue) { TraceLog.Informational(string.Format("Releasing lease for partition {0} due to an error, reason: {1}!", context.PartitionKeyRangeId, closeReason.Value)); // Note: this has to be a new task, because OnPartitionReleasedAsync awaits for worker task. await Task.Factory.StartNew(async() => await this.partitionManager.TryReleasePartitionAsync(context.PartitionKeyRangeId, true, closeReason.Value)); } TraceLog.Informational(string.Format("Partition {0}: worker finished!", context.PartitionKeyRangeId)); }); var newWorkerData = new WorkerData(workerTask, observer, context, cancellation); this.partitionKeyRangeIdToWorkerMap.AddOrUpdate(context.PartitionKeyRangeId, newWorkerData, (string id, WorkerData d) => { return(newWorkerData); }); }
public IChangeFeedDocumentQuery <Document> CreateDocumentChangeFeedQuery(string collectionLink, ChangeFeedOptions feedOptions) { return(new QoSMeteringChangeFeedDocumentQuery(_inner.CreateDocumentChangeFeedQuery(collectionLink, feedOptions), feedOptions.PartitionKeyRangeId, _meter)); }