/// <summary> /// Internal method to rebalance data across a different number of partitions. /// </summary> /// <param name="newPartitionCount">The target number of partitions.</param> /// <returns>The Task object for the asynchronous execution.</returns> private async Task<int> RepartitionData(int newPartitionCount) { // No-op, just delete the last collection. if (newPartitionCount == 0) { return 0; } ManagedHashPartitionResolver currentResolver = (ManagedHashPartitionResolver)this.Client.PartitionResolvers[this.Database.SelfLink]; var nextPartitionResolver = new ManagedHashPartitionResolver( currentResolver.PartitionKeyExtractor, this.Client, this.Database, newPartitionCount); TransitionHashPartitionResolver transitionaryResolver = new TransitionHashPartitionResolver( currentResolver, nextPartitionResolver, this.ReadMode); this.Client.PartitionResolvers[this.Database.SelfLink] = transitionaryResolver; // Move data between partitions. Here it's one by one, but you can change this to implement inserts // in bulk using stored procedures (bulkImport and bulkDelete), or run them in parallel. Another // improvement to this would be push down the check for partitioning function down to the individual // collections as a LINQ/SQL query. int numberOfMovedDocuments = 0; foreach (string collectionLink in currentResolver.CollectionLinks) { ResourceFeedReader<Document> feedReader = this.Client.CreateDocumentFeedReader(collectionLink); while (feedReader.HasMoreResults) { foreach (Document document in await feedReader.ExecuteNextAsync()) { object partitionKey = nextPartitionResolver.GetPartitionKey(document); string newCollectionLink = nextPartitionResolver.ResolveForCreate(partitionKey); if (newCollectionLink != collectionLink) { numberOfMovedDocuments++; await this.Client.DeleteDocumentAsync(document.SelfLink); await this.Client.CreateDocumentAsync(newCollectionLink, document); } } } } this.Client.PartitionResolvers[this.Database.SelfLink] = nextPartitionResolver; return numberOfMovedDocuments; }
/// <summary> /// Initialize a "managed" HashPartitionResolver that also takes care of creating collections, and cloning collection properties like /// stored procedures, offer type and indexing policy. /// </summary> /// <param name="partitionKeyExtractor">The partition key extractor function. (Ex: "u => ((UserProfile)u).UserId")</param> /// <param name="client">The DocumentDB client instance to use.</param> /// <param name="database">The database to run the samples on.</param> /// <param name="numCollections">The number of collections to be used.</param> /// <param name="collectionSpec">The DocumentCollectionSpec to be used for each collection created. If null returns Spec with defaultOfferType as set in config.</param> /// <returns>The created HashPartitionResolver.</returns> public static ManagedHashPartitionResolver InitializeManagedHashResolver(Func<object, string> partitionKeyExtractor, DocumentClient client, Database database, int numCollections, DocumentCollectionSpec collectionSpec) { // If no collectionSpec used, use a default spec with the offerType (ie: S1, S2, S3) that is specified in config. if (collectionSpec == null) { var hashResolver = new ManagedHashPartitionResolver(partitionKeyExtractor, client, database, numCollections, null, new DocumentCollectionSpec { OfferType = defaultOfferType }); client.PartitionResolvers[database.SelfLink] = hashResolver; return hashResolver; } // If there is a collectionSpec passed as a parameter, use that instead of default. else { var hashResolver = new ManagedHashPartitionResolver(partitionKeyExtractor, client, database, numCollections, null, collectionSpec); client.PartitionResolvers[database.SelfLink] = hashResolver; return hashResolver; } }