Tenho recebido perguntas de como calcular o custo do Azure para compará-lo com o custo de outras alternativas.
Já existem algumas calculadoras disponíveis para isto, mas, infelizmente todas as que eu vi fazem contas simples, que poderiam ser feitas com o Excel com pouco esforço, e que não levam em conta a variação probabilística de alguns fatores que influenciam no preço final.
Quando temos variáveis como GB de storage utilizados no mês, trafego de mensagens, número de transações, etc., melhor do que fazer uma conta com um número estimado (nosso “chute”) é utilizar uma estimativa estatística baseada em algum comportamento padrão – e aqui entram as distribuições normais, uniformes, binárias, etc.
Vamos a um exemplo. Distribuições normais são interessantes para modelar distribuições que tendem a se aglomerar perto de uma média – o que acontece, a meu ver, com todas as variáveis componentes do preço final do uso do Azure à exceção do custo de uso de CPU e SQL.
Outro fator interessante de uma distribuição normal é que podemos simular seu comportamento empiricamente se soubermos dois dados que nos dão os limites do intervalo de confiança em que temos 90% de confiança (ver figura).
E aqui um ponto importante: “chutar” um intervalo de confiança é mais fácil e preciso (!) do que “chutar” o valor esperado. Por exemplo, no Excel, bastaria utilizarmos a fórmula
=norminv(rand(),média,desvioPadrão)
onde
média = (Limite 90% do Intervalo de Certeza + Limite 10% do Intervalo de Certeza)/2
desvioPadrão = (Limite 90% do Intervalo de Certeza - Limite 10% do Intervalo de Certeza)/3,29
Levar em conta valores probabilísticos nos leva a um resultado probabilístico também. Isto é, ao invés de obtermos um único número obtemos uma curva probabilística que nos conta nossa variação com o risco. Por exemplo, podemos perceber que temos 10% de chance de pagar um valor de 800 quando o valor médio estaria em 700 e, ao final, saber estas chances pode nos levar a um processo de decisão mais realista – numa comparação entre preços ofertados no mercado, será factível compreender melhor quando ou com que probabilidade uma composição de preço é melhor que outra.
Como isto é um instrumento útil não só para cálculo de preços, mas para planejamento de capacidades e outros, vale a pena mostrar o como calcular levando em conta as curvas de probabilidades.
A maneira mais comum e genérica que temos hoje para calcular uma expressão que é constituída da composição de fórmulas probabilísticas é o método de Monte Carlo. O método de Monte Carlo é um método de simulação - isto é, são gerados vários números que obedecem as curvas de probabilidade de cada fórmula estatística da expressão. Por exemplo: se temos o custo em GB de storage e o custo de transferência de GB dados, podemos pensar que a expressão do custo dos dois é dado por P_usoGb()*custoPorGB + P_transfGB()*custoTransfPorGB. Como cada função P é de fato uma curva probabilística, podemos simular milhares de resultados ao gerar números (que obedeçam a curva de distribuição de cada função P_) para P_usoGb() e P_transfGB() e calculando milhares de resultados para esta expressão.
Com milhares de resultados, podemos agora calcular intervalos e suas probabilidades contando quantos dados simulados ficaram abaixo de um determinado valor. Outra maneira de lidar com isto é ordenar o array de resultados e calcular, por exemplo, 10 intervalos a partir dos valores mínimos e máximos da amostra. Sabendo os intervalos, podemos contar o total de valores dentro de cada intervalo e, com isto, temos a probabilidade para cada intervalo.
Um Excel ou um programa ajuda no cálculo. Como gosto de programas, vou contar como fiz minha versão 0.01 da minha calculadora do Azure.
Passo 1: Interface – Usei WPF pensando em usar gráficos no futuro.
Passo 2: Funções de geração de distribuição aleatórias para serem usadas na simulação – Coloquei em uma biblioteca 3 tipos de distribuição, mas uso apenas a distribuição normal aqui (de um algorítmo achado na internet). Aqui vai o código:
public class MathHelp
{
static Random r = new Random();
// Gera número randômico segundo a distribuição normal
// decimal upper90 => valor superior do Intervalo de 90% de Confiança
// decimal low90 => valor inferior do Intervalo de 90% de Confiança
public static double GenByNormal(double upper90, double low90)
{
double norminv = 0;
double probability = r.NextDouble();
double mean = (upper90 + low90)/2;
double sigma = (upper90 - low90)/ 3.29; // standard deviation
double x = 0;
double p = 0;
double t = 0;
double q = 0;
const double c0 = 2.515517, c1 = 0.802853, c2 = 0.010328;
const double d1 = 1.432788, d2 = 0.189269, d3 = 0.001308;
q = probability;
if (q == 0.5) norminv = mean;
else {
q = 1 - q;
if ((q >= 0) && (q < 0.5)) p = q;
else {
if (q == 1.0) p = 1 - 0.9999999; // JPR - attempt to fix divide by zero below, what is NormInv(1,x,y)?
else p = 1 - q;
}
t = Math.Sqrt(Math.Log(1 / (p * p)));
x = t - (c0 + c1 * t + c2 * (t * t)) / (1 + d1 * t + d2 * (t * t) + d3 * (t * t * t));
if (q > 0.5) x = -1 * x;
norminv = (x * sigma) + mean;
}
return norminv;
}
// Gera número randômico segundo a distribuição uniforme
// decimal upper => valor superior do Intervalo
// decimal low => valor inferior do Intervalo
public static double GenByUniform(double upper, double low)
{
return r.NextDouble() * (upper - low) + low;
}
// Gera número randômico segundo a distribuição binária
// decimal oneProb => probabilidade de dar 1
public static double GenByBin(double oneProb)
{
return ((r.NextDouble()> oneProb)?1:0);
}
}
Passo3: Criação dos arrays com valores aleatórios – Note que minha amostragem é de 1000 simulações para cada variável. Também vale a pena lembrar dos custos do Azure aqui ou aqui.
// Cria array de 1000 posições para cada par Upp-Lower
double[] cpuRequest = GenNormalArray(1000, 0.15, convertDoubleToString(txReqUpp.Text), convertDoubleToString(txReqLow.Text));
double[] kTrans = GenNormalArray(1000, 0.001, convertDoubleToString(txKTransacoesUpp90.Text), convertDoubleToString(txKTransacoesLow10.Text));
double[] gbUsados = GenNormalArray(1000, 0.15, convertDoubleToString(txGBUsadosUpp.Text), convertDoubleToString(txGBUsadosLow.Text));
double[] storageRead = GenNormalArray(1000, 0.15, convertDoubleToString(txLeituraUpp.Text), convertDoubleToString(txLeituraLow.Text));
double[] storageWrite = GenNormalArray(1000, 0.15, convertDoubleToString(txEscritaUpp.Text), convertDoubleToString(txEscritaLow.Text));
double[] sqlRead = GenNormalArray(1000, 0.15, convertDoubleToString(txSQLLeituraUpp.Text), convertDoubleToString(txSQLLeituraLow.Text));
double[] sqlWrite = GenNormalArray(1000, 0.15, convertDoubleToString(txSQLEscritaUpp.Text), convertDoubleToString(txSQLEscritaUpp.Text));
onde
private double convertDoubleToString(string s)
{
if ( s==String.Empty || s == "" ) s = "0";
double result = 0;
try
{
result = Convert.ToDouble(s);
}
catch (Exception err)
{
throw new Exception("Erro de digitação: " + err.Message);
}
return result;
}
private double[] GenNormalArray(int size, double value, double upper90, double low90)
{
double[] arr = new double[size];
for (int i = 0; i < size; i++)
{
arr[i] = MathHelp.GenByNormal(upper90, low90) * value;
}
return arr;
}
Passo 4: Cálculo do custo total para cada linha
double[] total = new double[1000];
double custoCPUs = convertDoubleToString(txNumCPUs.Text) * 24 * 30.5 * 0.12;
double custoSQL = convertDoubleToString(txNumSQL10GB.Text) * 99.99 + convertDoubleToString(txNumSQL1GB.Text) * 9.99;
for (int i = 0; i < 1000; i++)
{
// calcula custo Azure
total[i] = custoCPUs + cpuRequest[i] + gbUsados[i] + storageRead[i] + storageWrite[i] + kTrans[i];
if (custoSQL != 0)
{
total[i] += custoSQL + sqlRead[i] + sqlWrite[i];
}
}
Passo 5: Criação dos intervalos – Neste código deixei um valor fixo de 10 intervalos.
// Sort nos resultados para poder achar os intervalos
Array.Sort(total);
// cria 10 intervalos de acordo com o s valores mínimos e máximos encontrados
double interval = (total[999] - total[0]) / 10;
int[] counters = new int[10]; // array para contar número de resultados a cada intervalo
int index = 0;
counters[index] = 0;
double toCompare = total[0] + interval; // valor de teste
// inicia a contagem
for (int j = 0; j < 1000; j++)
{
if (total[j] >= toCompare)
{
index++;
toCompare += interval;
}
if (index < 10) counters[index]++;
else break;
}
Passo 6: Mostra dos resultados – Nesta versão, só um MessageBox
string s = "";
for (int i = 0; i < 10; i++)
{
s += "\n Maior Igual a " + Convert.ToString(total[0] + interval * i) + " val " + counters[i];
}
MessageBox.Show(s);
Note, neste exemplo, que temos uma variação de custo em dólares de ~290 a ~385, com valor mais provável no intervalo entre 322 a 354 (com ~78,5% de chance).
É claro que é uma versão draft com muitos defeitos. Por exemplo, o tratamento de erros é fraco e um gráfico final seria bom para melhorar sua venda. Mas a intenção é só mostrar um método de avaliação quando temos a composição de valores probabilísticos. Qualquer erro, por favor, e-mail para mim.
O importante é que este método de cálculo pode ser usado com o Azure, com preços de terceiros ou mesmo com o cálculo dos custos da sua TI. Lembre-se, de no custo total, colocar preços como eletricidade, custo de pessoal, etc.
Abraços