Reputation: 226
I am trying to replace the throughput of a manual container to autoscale using this code
container.ReplaceThroughputAsync(ThroughputProperties.CreateAutoscaleThroughput(4000));
This throws an exception. Errors":["x-ms-cosmos-migrate-offer-to-autopilot must be supplied and offercontent must not contain autopilotSettings for migration from manual throughput to autoscale."
Not able to find anything related to this on CosmosDB Documentation. I am currently using CosmosDB 3.12 V3 .Net SDK.
Upvotes: 4
Views: 7891
Reputation: 67
Note: Not an answer to this question but related to changing throughput programmatically when cosmos-db database on autoscale mode.
using Microsoft.Azure.Cosmos;
public async Task<ThroughputResponse> UpdateThroughput(int targetLevel)
{
Container container = cosmosClient.GetContainer(databaseName, collectionName);
ThroughputResponse throughput = await container.ReplaceThroughputAsync(ThroughputProperties.CreateAutoscaleThroughput(targetLevel));
return throughput;
}
Use case: I have to run a nightly job for an hour which has high throughput requirements (50K RU/s) but my normal load is not higher than (10K RU/s). At start of this job, I increase auto-scale to 50K and after the job is complete, I reduce it 10K to optimize costs. Since, cost ranges from (10% of x) to x, I want to maintain a cost-optimal threshold.
For further reading: https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/cosmos-db/how-to-provision-autoscale-throughput.md#change-the-autoscale-max-throughput-rus
Upvotes: 1
Reputation: 879
The Azure Cosmos DB "Replace an Offer" REST API method allows switching throughput modes between manual and autoscale. I won't reproduce the entirety of that method's documentation here, but the gist is that depending on which direction you are going, you must provide a special value in the "content" property of the body, as well as a specific custom HTTP request header.
{ "offerThroughput": -1 }
x-ms-cosmos-migrate-offer-to-autopilot=true
{ "offerAutopilotSettings": {"maxThroughput": -1} }
x-ms-cosmos-migrate-offer-to-manual-throughput=true
Working with the REST API is quite a bit trickier than the SDK. The following C# class wraps up changing the throughput method. You'll need the nuget package "Microsoft.Azure.Cosmos".
using System;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using Newtonsoft.Json;
namespace ThroughputChangerDemo
{
public class ThroughputChanger
{
private readonly HttpClient HttpClient;
public ThroughputChanger(HttpClient httpClient)
{
HttpClient = httpClient;
}
public async Task SetThroughput(Database database, bool autoScale, int maxThroughput)
{
ThroughputResponse oldThroughputResponse = await database.ReadThroughputAsync(new RequestOptions { });
var currentThroughput = oldThroughputResponse.Resource;
ThroughputProperties newThroughput = GenerateThroughputProperties(autoScale, maxThroughput);
if (currentThroughput.IsAutoScale() != autoScale)
{
await this.ChangeScalingMethodology(database.Client, currentThroughput, GenerateDatabaseLink(database));
}
await database.ReplaceThroughputAsync(newThroughput);
}
public async Task SetThroughput(Container container, bool autoScale, int maxThroughput)
{
ThroughputResponse oldThroughputResponse = await container.ReadThroughputAsync(new RequestOptions { });
var currentThroughput = oldThroughputResponse.Resource;
ThroughputProperties newThroughput = GenerateThroughputProperties(autoScale, maxThroughput);
if (currentThroughput.IsAutoScale() != autoScale)
{
await this.ChangeScalingMethodology(container.Database.Client, currentThroughput, GenerateContainerLink(container));
}
await container.ReplaceThroughputAsync(newThroughput);
}
/// <summary>
/// Toggle between Autoscale and Manual scaling methodologies for a database or container.
/// </summary>
/// <param name="currentThroughput"></param>
/// <param name="scalableItemLink">The resource link for the database or container to be changed</param>
/// <returns></returns>
private async Task ChangeScalingMethodology(CosmosClient client, ThroughputProperties currentThroughput,
string scalableItemLink)
{
bool changeToAutoScale = !currentThroughput.IsAutoScale();
// Attempt to change between scaling schemes...
string offerId = currentThroughput.SelfLink.Split('/')[1];
string offerResource = $"offers/{offerId}";
var url = $"{client.Endpoint.Scheme}://{client.Endpoint.Host}/{offerResource}";
var restEndpointUri = new Uri(url);
var method = HttpMethod.Put;
var httpDate = DateTime.UtcNow.ToString("R");
string auth = GenerateAuthToken(method, "offers", offerId,
httpDate, extractAuthKey());
var request = new HttpRequestMessage
{
RequestUri = restEndpointUri,
Method = method,
Headers = {
{ HttpRequestHeader.Authorization.ToString(), auth },
{ "x-ms-version", "2018-12-31" },
{ "x-ms-date", httpDate },
},
Content = new StringContent(JsonConvert.SerializeObject(createOffer()))
};
if (changeToAutoScale)
{
request.Headers.Add("x-ms-cosmos-migrate-offer-to-autopilot", "true");
}
else
{
request.Headers.Add("x-ms-cosmos-migrate-offer-to-manual-throughput", "true");
}
HttpResponseMessage putResponse = await HttpClient.SendAsync(request);
if (!putResponse.IsSuccessStatusCode)
{
var content = await putResponse.Content.ReadAsStringAsync();
throw new Exception($"Error changing throughput scheme: '{putResponse.ReasonPhrase}'.\nContent: {content}");
}
// local function
object createOffer()
{
// Read the ResourceRID using reflection because the property is protected.
string resourceRID = currentThroughput.GetType()
.GetProperty("ResourceRID", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(currentThroughput).ToString();
string resourceLink = scalableItemLink;
object content;
if (changeToAutoScale)
{
content = new
{
offerThroughput = -1
};
}
else
{
content = new
{
offerAutopilotSettings = new { maxThroughput = -1 }
};
}
return new
{
offerVersion = "V2",
offerType = "Invalid",
content = content,
resource = resourceLink,
offerResourceId = resourceRID,
id = offerId,
_rid = offerId,
};
}
string extractAuthKey()
{
// Read the AccountKey using reflection because the property is protected.
return client.GetType()
.GetProperty("AccountKey", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(client).ToString();
}
}
private string GenerateDatabaseLink(Database database) => $"dbs/{database.Id}/";
private string GenerateContainerLink(Container container) => $"dbs/{container.Database.Id}/colls/{container.Id}/";
private static ThroughputProperties GenerateThroughputProperties(bool autoScale, int? maxThroughput = null)
{
if (!autoScale)
{
if (!maxThroughput.HasValue || maxThroughput < 400)
maxThroughput = 400;
return ThroughputProperties.CreateManualThroughput(maxThroughput.Value);
}
else
{
if (!maxThroughput.HasValue || maxThroughput < 4000)
maxThroughput = 4000;
return ThroughputProperties.CreateAutoscaleThroughput(maxThroughput.Value);
}
}
/// <summary>
/// Generate the HTTP authorization header value needed to connect with Cosmos DB
/// </summary>
/// <param name="method">The Verb portion of the string is the HTTP verb, such as GET, POST, or PUT.</param>
/// <param name="resourceType">The ResourceType portion of the string identifies the type of resource that the request is for, Eg. "dbs", "colls", "docs".</param>
/// <param name="resourceLink">The ResourceLink portion of the string is the identity property of the resource that the request is directed at. ResourceLink must maintain its case for the ID of the resource. Example, for a container it looks like: "dbs/MyDatabase/colls/MyContainer".</param>
/// <param name="date">The Date portion of the string is the UTC date and time the message was sent (in "HTTP-date" format as defined by RFC 7231 Date/Time Formats), for example, Tue, 01 Nov 1994 08:12:31 GMT. In C#, it can be obtained by using the "R" format specifier on the DateTime.UtcNow value. This same date(in same format) also needs to be passed as x-ms-date header in the request.</param>
/// <param name="key">Cosmos DB key token (found in the Azure Portal)</param>
/// <param name="keyType">denotes the type of token: master or resource.</param>
/// <param name="tokenVersion">denotes the version of the token, currently 1.0.</param>
/// <returns></returns>
// This method borrowed from: https://learn.microsoft.com/en-us/rest/api/cosmos-db/access-control-on-cosmosdb-resources
private string GenerateAuthToken(HttpMethod method, string resourceType, string resourceLink,
string date, string key, string keyType = "master", string tokenVersion = "1.0")
{
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
var verb = method?.Method ?? "";
resourceType = resourceType ?? "";
resourceLink = resourceLink?.ToLower() ?? ""; // Without ToLower(), we get an 'unauthorized' error.
string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
verb.ToLowerInvariant(),
resourceType.ToLowerInvariant(),
resourceLink,
date.ToLowerInvariant(),
""
);
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);
return System.Web.HttpUtility.UrlEncode(String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
keyType,
tokenVersion,
signature));
}
}
}
Usage:
var httpClient = new HttpClient();
var cosmosClient = new CosmosClient(EndpointUrl, PrimaryKey);
var database = cosmosClient.GetDatabase(DatabaseId);
var changer = new ThroughputChanger(httpClient);
await changer.SetThroughput(database, autoScale: true, maxThroughput: 8000);
httpClient.Dispose();
Upvotes: 3
Reputation: 8660
Change throughput from manual to autoscale by sdk is not supported now.Method ReplaceThroughputAsync
only can change the throughput.You should change this on Azure portal.
Upvotes: 3