Autopsy of AWS EC2 pricing - and some anomalies

Summary tables

Intro

At Strategic Blue, we care about the money, and we’ve been collecting prices of AWS and other cloud vendors for a while. Another characteristic of Strategic Blue is that we are curious and love digging into the numbers, checking common assumptions, and find new ways to save money.

  • The first article in the series focused on finding the cost of Windows on AWS and make some basic comparison with Azure
  • A second article show an anomaly in AWS pricing probably due to a human error

This article extends on those researches to split AWS pricing into three components:

  1. the cost of the infrastructure
  2. the cost of the OS licence
  3. the cost of the software (SQL Server) license running on those instances

This is based only on EC2 data but could be replicated on other AWS services.

Note that for all tables there are some exceptions which are covered at the end of this article

Executive Summary

  • All the pricing are consistent, showing great care on AWS part to charge consistently in each region
  • There are lots of little exceptions that seem to be more human errors in entering the data

POMO

Purpose: to find if a good approximation of the cost of the infrastructure, Os, and software licensing on AWS Objective: being able to separate infrastructure and licensing costs Method: SQL queries on AWS pricing table Outcome: A set of tables license price list for AWS

Assumptions:

  • Work is done on shared instances
  • the infrastructure cost is the cost of a Linux VM
  • The OS cost is instance price inc. OS - Linux instance price
  • The Software cost is the hourly price - infrastructure price - os license price
  • Based on April 2020 prices

Other possible experiments:

  • check RI pricing logic - are RI calculated on the infrastructure cost then a license is added
  • try to extract the different components of the infrastructure by making assumptions
  • improve the queries
  • make this analysis automated
  • play with the estimated cost of DC supply (e.g. real-estate, electricity) between regions

Summary tables

Linux

is always 0 and is considered the price of the infrastructure.

Suse

SUSE being not standard the list is long

gen size os oslicprice
m4 10xlarge SUSE 0.100
c5 12xlarge SUSE 0.100
c5d 12xlarge SUSE 0.100
g4dn 12xlarge SUSE 0.150
i3en 12xlarge SUSE 0.150
m5 12xlarge SUSE 0.100
m5a 12xlarge SUSE 0.150
m5ad 12xlarge SUSE 0.150
m5d 12xlarge SUSE 0.100
m5dn 12xlarge SUSE 0.150
m5n 12xlarge SUSE 0.150
m6g 12xlarge SUSE 0.150
r5 12xlarge SUSE 0.150
r5a 12xlarge SUSE 0.150
r5ad 12xlarge SUSE 0.150
r5d 12xlarge SUSE 0.150
r5dn 12xlarge SUSE 0.150
r5n 12xlarge SUSE 0.150
z1d 12xlarge SUSE 0.150
f1 16xlarge SUSE 0.100
g3 16xlarge SUSE 0.100
g4dn 16xlarge SUSE 0.150
h1 16xlarge SUSE 0.100
i3 16xlarge SUSE 0.100
m4 16xlarge SUSE 0.100
m5 16xlarge SUSE 0.100
m5a 16xlarge SUSE 0.150
m5ad 16xlarge SUSE 0.150
m5d 16xlarge SUSE 0.100
m5dn 16xlarge SUSE 0.150
m5n 16xlarge SUSE 0.150
m6g 16xlarge SUSE 0.150
p2 16xlarge SUSE 0.100
p3 16xlarge SUSE 0.100
r4 16xlarge SUSE 0.100
r5 16xlarge SUSE 0.150
r5a 16xlarge SUSE 0.150
r5ad 16xlarge SUSE 0.150
r5d 16xlarge SUSE 0.150
r5dn 16xlarge SUSE 0.150
r5n 16xlarge SUSE 0.150
x1 16xlarge SUSE 0.100
x1e 16xlarge SUSE 0.100
c5 18xlarge SUSE 0.100
c5d 18xlarge SUSE 0.100
c5n 18xlarge SUSE 0.150
c5 24xlarge SUSE 0.100
c5d 24xlarge SUSE 0.100
i3en 24xlarge SUSE 0.150
inf1 24xlarge SUSE 0.150
m5 24xlarge SUSE 0.100
m5a 24xlarge SUSE 0.150
m5ad 24xlarge SUSE 0.150
m5d 24xlarge SUSE 0.100
m5dn 24xlarge SUSE 0.150
m5n 24xlarge SUSE 0.150
p3dn 24xlarge SUSE 0.150
r5 24xlarge SUSE 0.150
r5a 24xlarge SUSE 0.150
r5ad 24xlarge SUSE 0.150
r5d 24xlarge SUSE 0.150
r5dn 24xlarge SUSE 0.150
r5n 24xlarge SUSE 0.150
a1 2xlarge SUSE 0.150
c4 2xlarge SUSE 0.100
c5 2xlarge SUSE 0.100
c5d 2xlarge SUSE 0.100
c5n 2xlarge SUSE 0.150
d2 2xlarge SUSE 0.100
f1 2xlarge SUSE 0.100
g4dn 2xlarge SUSE 0.150
h1 2xlarge SUSE 0.100
i3 2xlarge SUSE 0.100
i3en 2xlarge SUSE 0.150
inf1 2xlarge SUSE 0.150
m4 2xlarge SUSE 0.100
m5 2xlarge SUSE 0.100
m5a 2xlarge SUSE 0.150
m5ad 2xlarge SUSE 0.150
m5d 2xlarge SUSE 0.100
m5dn 2xlarge SUSE 0.150
m5n 2xlarge SUSE 0.150
m6g 2xlarge SUSE 0.150
p3 2xlarge SUSE 0.100
r4 2xlarge SUSE 0.100
r5 2xlarge SUSE 0.150
r5a 2xlarge SUSE 0.150
r5ad 2xlarge SUSE 0.150
r5d 2xlarge SUSE 0.150
r5dn 2xlarge SUSE 0.150
r5n 2xlarge SUSE 0.150
t2 2xlarge SUSE 0.100
t3 2xlarge SUSE 0.150
t3a 2xlarge SUSE 0.150
x1e 2xlarge SUSE 0.100
z1d 2xlarge SUSE 0.150
x1 32xlarge SUSE 0.100
x1e 32xlarge SUSE 0.100
i3en 3xlarge SUSE 0.150
z1d 3xlarge SUSE 0.150
a1 4xlarge SUSE 0.150
c4 4xlarge SUSE 0.100
c5 4xlarge SUSE 0.100
c5d 4xlarge SUSE 0.100
c5n 4xlarge SUSE 0.150
d2 4xlarge SUSE 0.100
f1 4xlarge SUSE 0.100
g3 4xlarge SUSE 0.100
g4dn 4xlarge SUSE 0.150
h1 4xlarge SUSE 0.100
i3 4xlarge SUSE 0.100
m4 4xlarge SUSE 0.100
m5 4xlarge SUSE 0.100
m5a 4xlarge SUSE 0.150
m5ad 4xlarge SUSE 0.150
m5d 4xlarge SUSE 0.100
m5dn 4xlarge SUSE 0.150
m5n 4xlarge SUSE 0.150
m6g 4xlarge SUSE 0.150
r4 4xlarge SUSE 0.100
r5 4xlarge SUSE 0.150
r5a 4xlarge SUSE 0.150
r5ad 4xlarge SUSE 0.150
r5d 4xlarge SUSE 0.150
r5dn 4xlarge SUSE 0.150
r5n 4xlarge SUSE 0.150
x1e 4xlarge SUSE 0.100
i3en 6xlarge SUSE 0.150
inf1 6xlarge SUSE 0.150
z1d 6xlarge SUSE 0.150
c4 8xlarge SUSE 0.100
d2 8xlarge SUSE 0.100
g3 8xlarge SUSE 0.100
g4dn 8xlarge SUSE 0.150
h1 8xlarge SUSE 0.100
i3 8xlarge SUSE 0.100
m5 8xlarge SUSE 0.100
m5a 8xlarge SUSE 0.150
m5ad 8xlarge SUSE 0.150
m5d 8xlarge SUSE 0.100
m5dn 8xlarge SUSE 0.150
m5n 8xlarge SUSE 0.150
m6g 8xlarge SUSE 0.150
p2 8xlarge SUSE 0.100
p3 8xlarge SUSE 0.100
r4 8xlarge SUSE 0.100
r5 8xlarge SUSE 0.150
r5a 8xlarge SUSE 0.150
r5ad 8xlarge SUSE 0.150
r5d 8xlarge SUSE 0.150
r5dn 8xlarge SUSE 0.150
r5n 8xlarge SUSE 0.150
x1e 8xlarge SUSE 0.100
c5 9xlarge SUSE 0.100
c5d 9xlarge SUSE 0.100
c5n 9xlarge SUSE 0.150
a1 large SUSE 0.063
c4 large SUSE 0.100
c5 large SUSE 0.100
c5d large SUSE 0.100
c5n large SUSE 0.063
i3 large SUSE 0.100
i3en large SUSE 0.063
m4 large SUSE 0.100
m5 large SUSE 0.100
m5a large SUSE 0.063
m5ad large SUSE 0.063
m5d large SUSE 0.100
m5dn large SUSE 0.063
m5n large SUSE 0.063
m6g large SUSE 0.063
r4 large SUSE 0.100
r5 large SUSE 0.063
r5a large SUSE 0.063
r5ad large SUSE 0.063
r5d large SUSE 0.063
r5dn large SUSE 0.063
r5n large SUSE 0.063
t2 large SUSE 0.100
t3 large SUSE 0.063
t3a large SUSE 0.063
z1d large SUSE 0.063
a1 medium SUSE 0.031
m6g medium SUSE 0.031
t2 medium SUSE 0.100
t3 medium SUSE 0.063
t3a medium SUSE 0.063
t2 micro SUSE 0.010
t2 micro SUSE 0.012
t2 micro SUSE 0.100
t3 micro SUSE 0.000
t3a micro SUSE 0.000
t2 nano SUSE 0.010
t2 nano SUSE 0.011
t2 nano SUSE 0.100
t3 nano SUSE 0.000
t3a nano SUSE 0.000
t2 small SUSE 0.030
t2 small SUSE 0.034
t2 small SUSE 0.100
t3 small SUSE 0.031
t3a small SUSE 0.031
a1 xlarge SUSE 0.125
c4 xlarge SUSE 0.100
c5 xlarge SUSE 0.100
c5d xlarge SUSE 0.100
c5n xlarge SUSE 0.125
d2 xlarge SUSE 0.100
g3s xlarge SUSE 0.125
g4dn xlarge SUSE 0.125
i3 xlarge SUSE 0.100
i3en xlarge SUSE 0.125
inf1 xlarge SUSE 0.125
m4 xlarge SUSE 0.100
m5 xlarge SUSE 0.100
m5a xlarge SUSE 0.125
m5ad xlarge SUSE 0.125
m5d xlarge SUSE 0.100
m5dn xlarge SUSE 0.125
m5n xlarge SUSE 0.125
m6g xlarge SUSE 0.125
p2 xlarge SUSE 0.100
r4 xlarge SUSE 0.100
r5 xlarge SUSE 0.125
r5a xlarge SUSE 0.125
r5ad xlarge SUSE 0.125
r5d xlarge SUSE 0.125
r5dn xlarge SUSE 0.125
r5n xlarge SUSE 0.125
t2 xlarge SUSE 0.100
t3 xlarge SUSE 0.125
t3a xlarge SUSE 0.125
x1e xlarge SUSE 0.100
z1d xlarge SUSE 0.125
SELECT  gen, size, os, oslicprice
	FROM public.v_aws_infra_os_sfw
	where os = 'SUSE'
	group by gen, size, os, oslicprice
	order by size, gen, oslicprice
	;

RedHat

size os oslicprice
10xlarge RHEL 0.130
12xlarge RHEL 0.130
16xlarge RHEL 0.130
18xlarge RHEL 0.130
24xlarge RHEL 0.130
2xlarge RHEL 0.130
32xlarge RHEL 0.130
3xlarge RHEL 0.130
4xlarge RHEL 0.130
6xlarge RHEL 0.130
8xlarge RHEL 0.130
9xlarge RHEL 0.130
large RHEL 0.060
medium RHEL 0.060
micro RHEL 0.060
small RHEL 0.060
xlarge RHEL 0.060

For an unknown reason (mistake?) the r4.xlarge costs $0.13 when for all other families the price for xlarge is .06

gen size oslicprice
r4 xlarge 0.130
SELECT  size, os, oslicprice
	FROM public.v_aws_infra_os_sfw
	where os = 'RHEL'
	group by size, os, oslicprice
	order by size, oslicprice
	;

windows

size os oslicprice count
10xlarge Windows 1.840 15
12xlarge Windows 2.208 240
16xlarge Windows 2.944 291
18xlarge Windows 3.312 50
24xlarge Windows 4.416 214
2xlarge Windows 0.368 335
32xlarge Windows 5.888 27
3xlarge Windows 0.552 27
4xlarge Windows 0.736 309
6xlarge Windows 1.104 27
8xlarge Windows 1.472 261
9xlarge Windows 1.656 50
large Windows 0.092 291
xlarge Windows 0.184 340
SELECT  size, os, oslicprice, COUNT(*)
	FROM public.v_aws_infra_os_sfw
	where os = 'Windows'
	AND software = 'NA'
	AND gen NOT LIKE 't%'
	AND gen NOT LIKE 'd2'
	AND gen NOT LIKE 'c4'
	group by size, os, oslicprice
	order by size, oslicprice
	;

anomalies

d2 are priced differently

size os oslicprice count
2xlarge Windows 0.221 8
2xlarge Windows 0.368 10
4xlarge Windows 0.302 8
4xlarge Windows 0.736 10
8xlarge Windows 0.678 8
8xlarge Windows 1.656 10
xlarge Windows 0.131 8
xlarge Windows 0.184 10

c4.8xlarge are priced differently

size os oslicprice count
8xlarge Windows 1.500 3
8xlarge Windows 1.523 1
8xlarge Windows 1.543 1
8xlarge Windows 1.656 10

SQL Web

size software sfwprice cpu counting
large SQL Web 0.07 2 597
xlarge SQL Web 0.07 4 656
2xlarge SQL Web 0.14 8 652
4xlarge SQL Web 0.27 16 557
4xlarge SQL Web 0.28 16 60
6xlarge SQL Web 0.41 24 56
8xlarge SQL Web 0.54 32 420
8xlarge SQL Web 0.56 32 35
9xlarge SQL Web 0.61 36 100
10xlarge SQL Web 0.68 40 15
12xlarge SQL Web 0.81 48 480
16xlarge SQL Web 1.08 64 450
16xlarge SQL Web 1.13 64 49
18xlarge SQL Web 1.22 72 100
24xlarge SQL Web 1.62 96 422
32xlarge SQL Web 2.16 128 39

The query ignore when the number of occurrence of a price are < 12, this eliminates most strange behaviour in the pricing.

SELECT t1.size, software, ROUND(sfwprice,2) sfwprice, cpu, count(*) counting
	FROM public.v_aws_infra_os_sfw t1 JOIN aws_size2cpu t2 ON t1.size = t2.size
	where software = 'SQL Web'
	AND current_generation = 'Yes'
	AND gen NOT LIKE 't%'
	GROUP BY software, t1.size, cpu,  ROUND(sfwprice,2)
	HAVING count(*)  > 12
	ORDER BY cpu, software, sfwprice,  counting desc
	;

Anomalies

There are some Negative SQL Web pricing making it cheaper to get a Windows + SQL Web versus Windows only

region size os oslicprice sfwprice count
Asia Pacific (Sydney) 2xlarge Windows 0.368 -0.113 1
EU (Frankfurt) 2xlarge Windows 0.368 -0.113 1
EU (Ireland) 2xlarge Windows 0.368 -0.113 1
US East (N. Virginia) 2xlarge Windows 0.368 -0.113 1
US East (Ohio) 2xlarge Windows 0.368 -0.113 1
US West (N. California) 2xlarge Windows 0.368 -0.113 1
US West (Oregon) 2xlarge Windows 0.368 -0.113 1
SELECT  region, size, os, oslicprice, sfwprice, COUNT(*)
	FROM public.v_aws_infra_os_sfw
	where os = 'Windows'
	AND software = 'SQL Web'
	AND gen LIKE 'r4'
	AND size = '2xlarge'
	AND sfwprice < 0
	group by size, os, oslicprice, sfwprice, region
	order by size, oslicprice
	;

SQL Standard

size software sfwprice cpu counting
large SQL Std 0.48 2 597
xlarge SQL Std 0.48 4 657
2xlarge SQL Std 0.96 8 665
4xlarge SQL Std 1.92 16 607
6xlarge SQL Std 2.88 24 56
8xlarge SQL Std 3.84 32 454
9xlarge SQL Std 4.32 36 100
10xlarge SQL Std 4.80 40 30
12xlarge SQL Std 5.76 48 479
16xlarge SQL Std 7.68 64 510
18xlarge SQL Std 8.64 72 100
24xlarge SQL Std 11.52 96 422
32xlarge SQL Std 15.36 128 54

The query ignore when the number of occurrence of a price are < 12, this eliminates most strange behaviour in the pricing.

SELECT t1.size, software, ROUND(sfwprice,2) sfwprice, cpu, count(*) counting
	FROM public.v_aws_infra_os_sfw t1 JOIN aws_size2cpu t2 ON t1.size = t2.size
	where software = 'SQL Std'
	AND current_generation = 'Yes'
	AND gen NOT LIKE 't%'
  AND gen NOT LIKE 'd2'
	GROUP BY software, t1.size, cpu,  ROUND(sfwprice,2)
	HAVING count(*)  > 12
	ORDER BY cpu, software, sfwprice,  counting desc
	;
SELECT  size, os, oslicprice, COUNT(*)
	FROM public.v_aws_infra_os_sfw
	where os = 'Windows'
	AND software = 'NA'
	AND gen LIKE 'c4'
	AND size = '8xlarge'
	group by size, os, oslicprice
	order by size, oslicprice
	;

SQL Enterprise

size software sfwprice cpu counting
xlarge SQL Ent 1.50 4 700
2xlarge SQL Ent 3.00 8 708
4xlarge SQL Ent 6.00 16 650
6xlarge SQL Ent 9.00 24 56
8xlarge SQL Ent 12.00 32 455
8xlarge SQL Ent 13.50 32 44
9xlarge SQL Ent 13.50 36 100
10xlarge SQL Ent 15.00 40 30
12xlarge SQL Ent 18.00 48 480
16xlarge SQL Ent 24.00 64 512
18xlarge SQL Ent 27.00 72 100
24xlarge SQL Ent 36.00 96 422
32xlarge SQL Ent 48.00 128 54

The query ignore when the number of occurrence of a price are < 12, this eliminates most strange behaviour in the pricing.

SELECT t1.size, software, ROUND(sfwprice,2) sfwprice, cpu, count(*) counting
	FROM public.v_aws_infra_os_sfw t1 JOIN aws_size2cpu t2 ON t1.size = t2.size
	where software = 'SQL Ent'
	AND current_generation = 'Yes'
	AND gen NOT LIKE 't%'
	GROUP BY software, t1.size, cpu,  ROUND(sfwprice,2)
	HAVING count(*)  > 12
	ORDER BY cpu, software, sfwprice,  counting desc
	;

Infrastructure

The price per CPU per generation seems consistent. Here is a summary table (the big one is 1.2M of text)

gen Lowest price Mean price Max price Standard Deviation (smaller is better)
a1 0.013 0.025 0.033 0.006
c4 0.050 0.058 0.078 0.007
c5 0.043 0.050 0.066 0.006
c5d 0.048 0.057 0.075 0.006
c5n 0.054 0.061 0.071 0.006
d2 0.173 0.198 0.239 0.018
f1 0.206 0.222 0.248 0.016
g3 0.071 0.088 0.110 0.013
g3s 0.188 0.227 0.289 0.034
g4dn 0.068 0.105 0.224 0.031
h1 0.059 0.060 0.065 0.003
i3 0.078 0.091 0.125 0.010
i3en 0.113 0.131 0.180 0.015
inf1 0.073 0.081 0.092 0.007
m4 0.050 0.059 0.080 0.007
m5 0.048 0.057 0.077 0.007
m5a 0.028 0.049 0.069 0.009
m5ad 0.034 0.060 0.083 0.010
m5d 0.057 0.067 0.090 0.008
m5dn 0.068 0.076 0.088 0.008
m5n 0.060 0.067 0.077 0.007
m6g 0.019 0.036 0.039 0.007
p2 0.225 0.325 0.430 0.083
p3 0.383 0.456 0.529 0.061
p3dn 0.325 0.362 0.446 0.051
r4 0.067 0.079 0.140 0.017
r5 0.063 0.073 0.101 0.008
r5a 0.036 0.064 0.091 0.011
r5ad 0.042 0.074 0.104 0.013
r5d 0.072 0.084 0.114 0.009
r5dn 0.084 0.092 0.102 0.008
r5n 0.075 0.082 0.091 0.007
t2 0.003 0.035 0.074 0.022
t3 0.003 0.033 0.067 0.020
t3a 0.002 0.029 0.061 0.017
x1 0.104 0.136 0.203 0.027
x1e 0.209 0.259 0.406 0.045
z1d 0.093 0.105 0.114 0.008
SELECT gen, MIN(ROUND(infraprice/t2.cpu,3)) percpuprice_min, ROUND(AVG(infraprice/t2.cpu),3) percpuprice_avg, MAX(ROUND(infraprice/t2.cpu,3)) percpuprice_max, ROUND(stddev_samp(infraprice/t2.cpu),3) percpuprice_standard_deviation
	FROM public.v_aws_infra_os_sfw t1 JOIN aws_size2cpu t2 ON t1.size = t2.size
	where current_generation = 'Yes'
	GROUP BY gen  
	ORDER BY gen
	;
comments powered by Disqus