I tried getting Amazon RDS on-demand pricing using the aws pricing command

I tried getting Amazon RDS on-demand pricing using the aws pricing command

I tried bulk-fetching Amazon RDS on-demand prices using the AWS Price List Query API via AWS CLI. My hope of just specifying the DB engine and region was shattered.
2026.09.29

This page has been translated by machine translation. View original

Good evening, this is Chiba (Kō).

Recently, I've been interested in Amazon RDS Reserved Instances. The other day, I tried using the AWS CLI to bulk-retrieve the "effective hourly rate" for specific region/DB engine combinations. (From that, the main blog post became "checking available combinations of terms and payment options for purchasing," but...)

https://dev.classmethod.jp/articles/rds-ri-purchase-options-availability-guide/

Since I'm at it, I also want to bulk-retrieve the "effective cost savings rate compared to On-Demand pricing."

To do that, in addition to the "effective hourly rate for Reserved Instances" I already know how to retrieve, I also need to retrieve the "hourly On-Demand rate."

This can be retrieved using the AWS Price List Query API, but when I looked into it, it turned out to be quite complex. This time, I'll introduce how to use the aws pricing command — the AWS CLI command corresponding to the AWS Price List Query API — along with tips for retrieving On-Demand pricing for various DB engines.

(That alone turned into quite a lot, so comparing the retrieved On-Demand pricing with the effective hourly rate of Reserved Instances to calculate the cost savings rate will be saved for another time.)

Summary First

  • You can retrieve Amazon RDS On-Demand pricing with aws pricing get-attribute-values
    • Reserved Instance pricing can also be retrieved (not retrieved in this blog post)
  • There are several attributes to consider when running aws pricing get-attribute-values. These attributes differ depending on the DB engine

Tested Environment

This has been verified in the following environment.

  • macOS
  • Zsh version: zsh 5.9 (arm64-apple-darwin25.0)
  • AWS CLI version: aws-cli/2.36.19 Python/3.14.6 Darwin/25.5.0 exe/arm64
  • Jq version: jq-1.6

Commands Used in This Blog Post

In this blog post, I'll use three subcommands of aws pricing that correspond to the AWS Price List Query API.

Also, all commands share the following options.

  • --service-code AmazonRDS: Specifies the target service
  • --region us-east-1: Specifies the Pricing API endpoint
  • --output json: Outputs in JSON format for subsequent jq processing

--region refers to the API endpoint and is fixed to us-east-1. When specifying the region whose pricing you want to look up, specify it as the regionCode attribute.

Checking Available Filter Attributes and Values for Amazon RDS

When retrieving On-Demand pricing, you'll ultimately specify various attributes in the --filters of aws pricing get-products to narrow down results. First, let's check what attributes are available and what values can be specified.

The list of attribute names that can be specified in filters is retrieved with describe-services. Specify Amazon RDS as the service code and check the list.

aws pricing describe-services \
  --service-code AmazonRDS \
  --region us-east-1 \
  --output json \
| jq -r '.Services[0].AttributeNames[]' | sort -f

The results as of September 2026 are as follows.

Results
acu
clockSpeed
currentGeneration
databaseEdition
databaseEngine
dedicatedEbsThroughput
deploymentModel
deploymentOption
engineCode
engineMajorVersion
engineMediaType
enhancedNetworkingSupported
extendedSupportPricingYear
group
groupDescription
instanceFamily
instanceType
instanceTypeFamily
LeaseContractLength
licenseModel
limitlesspreview
location
locationType
maxVolumeSize
memory
minVolumeSize
networkPerformance
normalizationSizeFactor
OfferingClass
operation
physicalProcessor
processorArchitecture
processorFeatures
productFamily
PurchaseOption
regionCode
Restriction
servicecode
servicename
storage
storageMedia
termType
unbundledLicensing
usagetype
vcpu
volumeName
volumeType
windowslicensemultiplier
Version without jq processing
% aws pricing describe-services \
  --service-code AmazonRDS \
  --region us-east-1 \
  --output json
{
    "Services": [
        {
            "ServiceCode": "AmazonRDS",
            "AttributeNames": [
                "termType",
                "productFamily",
                "servicecode",
                "location",
                "locationType",
                "instanceType",
                "currentGeneration",
                "instanceFamily",
                "vcpu",
                "physicalProcessor",
                "memory",
                "storage",
                "networkPerformance",
                "processorArchitecture",
                "engineCode",
                "databaseEngine",
                "licenseModel",
                "deploymentOption",
                "usagetype",
                "operation",
                "instanceTypeFamily",
                "normalizationSizeFactor",
                "regionCode",
                "servicename",
                "unbundledLicensing",
                "LeaseContractLength",
                "PurchaseOption",
                "OfferingClass",
                "databaseEdition",
                "windowslicensemultiplier",
                "group",
                "groupDescription",
                "deploymentModel",
                "clockSpeed",
                "processorFeatures",
                "enhancedNetworkingSupported",
                "engineMediaType",
                "storageMedia",
                "volumeType",
                "minVolumeSize",
                "maxVolumeSize",
                "dedicatedEbsThroughput",
                "volumeName",
                "engineMajorVersion",
                "extendedSupportPricingYear",
                "acu",
                "Restriction",
                "limitlesspreview"
            ]
        }
    ],
    "FormatVersion": "aws_v1"
}

Then, the candidate values that can be specified for a specific attribute are retrieved with get-attribute-values. Below is an example specifying the databaseEngine attribute.

aws pricing get-attribute-values \
  --service-code AmazonRDS \
  --attribute-name databaseEngine \
  --region us-east-1 \
  --output json \
| jq -r '.AttributeValues[].Value'

The results are as follows.

Results
Any
Aurora MySQL
Aurora PostgreSQL
Db2
MariaDB
MySQL (on-premise for Outpost)
MySQL
Oracle (on-premises for Outposts)
Oracle
PostgreSQL (on-premise for Outpost)
PostgreSQL
SQL Server (on-premise for Outpost)
SQL Server
Version without jq processing
% aws pricing get-attribute-values \
  --service-code AmazonRDS \
  --attribute-name databaseEngine \
  --region us-east-1 \
  --output json
{
    "AttributeValues": [
        {
            "Value": "Any"
        },
        {
            "Value": "Aurora MySQL"
        },
        {
            "Value": "Aurora PostgreSQL"
        },
        {
            "Value": "Db2"
        },
        {
            "Value": "MariaDB"
        },
        {
            "Value": "MySQL (on-premise for Outpost)"
        },
        {
            "Value": "MySQL"
        },
        {
            "Value": "Oracle (on-premises for Outposts)"
        },
        {
            "Value": "Oracle"
        },
        {
            "Value": "PostgreSQL (on-premise for Outpost)"
        },
        {
            "Value": "PostgreSQL"
        },
        {
            "Value": "SQL Server (on-premise for Outpost)"
        },
        {
            "Value": "SQL Server"
        }
    ]
}

Now that I had a list of DB engine attribute values, I initially hoped that filtering by just the region and DB engine would give me the On-Demand pricing I wanted, but it wasn't that simple.

I also need to understand other attributes. Let me combine the two commands to list the main values for all attributes.

{
  echo "| Attribute | Count | Candidate Values |"
  echo "| --- | --- | --- |"
  for attr in $(aws pricing describe-services \
    --service-code AmazonRDS \
    --region us-east-1 \
    --output json \
  | jq -r '.Services[0].AttributeNames[]' | sort -f); do
    aws pricing get-attribute-values \
      --service-code AmazonRDS \
      --attribute-name "$attr" \
      --region us-east-1 \
      --output json \
    | jq -r --arg attr "$attr" '
        [.AttributeValues[].Value] as $v
        | ($v | length) as $n
        | if $n > 15
          then "| `\($attr)` | \($n) | \($v[0:4] | join(" / ")) …etc |"
          else "| `\($attr)` | \($n) | \($v | join(" / ")) |"
          end
      '
  done
}

The execution looks like this. Output is produced one line at a time.

| Attribute | Count | Candidate Values |
| --- | --- | --- |
| `acu` | 1 | 1 |
| `clockSpeed` | 5 | 2.3 GHz / 2.4 GHz / 2.5 GHz / Up to 3.1 GHz / Up to 3.3 GHz |
| `currentGeneration` | 2 | No / Yes |
| `databaseEdition` | 14 | Advanced / Any / Community / Developer / Enterprise Developer / Enterprise-BYOM / Enterprise / Express / Standard Developer / Standard One / Standard Two / Standard-BYOM / Standard / Web |
| `databaseEngine` | 13 | Any / Aurora MySQL / Aurora PostgreSQL / Db2 / MariaDB / MySQL (on-premise for Outpost) / MySQL / Oracle (on-premises for Outposts) / Oracle / PostgreSQL (on-premise for Outpost) / PostgreSQL / SQL Server (on-premise for Outpost) / SQL Server |
| `dedicatedEbsThroughput` | 27 | 1000 Mbps / 10000 Mbps / 12000 Mbps / 1250 Mbps …etc |
| `deploymentModel` | 1 | Custom |
| `deploymentOption` | 5 | Multi-AZ (SQL Server Mirror) / Multi-AZ (readable standbys) / Multi-AZ / Single-AZ (Oracle RAC) / Single-AZ |
| `engineCode` | 43 | 0 / 10 / 11 / 12 …etc |
| `engineMajorVersion` | 8 | 10 / 11 / 12 / 13 / 14 / 15 / 5.7 / 8.0 |
| `engineMediaType` | 2 | AWS-provided / Customer-provided |
| `enhancedNetworkingSupported` | 1 | Yes |
| `extendedSupportPricingYear` | 4 | Year 1, Year 2 (PP) / Year 1, Year 2 / Year 3 (PP) / Year 3 |
……abbreviated below

Since some attributes have over 20,000 values, I used an approach that truncates output when the count exceeds 15.

Below are the results as of September 2026, with notes added by the author.

Attribute Count Candidate Values Notes
acu 1 1 Aurora Capacity Unit. The capacity unit for Aurora Serverless
clockSpeed 6 2.3 GHz / 2.4 GHz / 2.5 GHz / Up to 3.0 GHz / Up to 3.1 GHz / Up to 3.3 GHz CPU clock frequency
currentGeneration 2 No / Yes Whether it's a current-generation instance. db.m1 series etc. are No
databaseEdition 14 Advanced / Any / Community / Developer / Enterprise Developer / Enterprise-BYOM / Enterprise / Express / Standard Developer / Standard One / Standard Two / Standard-BYOM / Standard / Web DB engine edition. Required to uniquely narrow down pricing for Oracle/SQL Server/Db2. Those with -BYOM are for RDS Custom
databaseEngine 13 Any / Aurora MySQL / Aurora PostgreSQL / Db2 / MariaDB / MySQL (on-premise for Outpost) / MySQL / Oracle (on-premises for Outposts) / Oracle / PostgreSQL (on-premise for Outpost) / PostgreSQL / SQL Server (on-premise for Outpost) / SQL Server DB engine type. Those with (on-premise for Outpost) are for AWS Outposts
dedicatedEbsThroughput 27 1000 Mbps / 10000 Mbps / 12000 Mbps / 1250 Mbps …etc Dedicated EBS bandwidth. Only exists up to db.m5/db.r5b generation
deploymentModel 1 Custom Only present in RDS Custom records
deploymentOption 5 Multi-AZ (SQL Server Mirror) / Multi-AZ (readable standbys) / Multi-AZ / Single-AZ (Oracle RAC) / Single-AZ AZ placement configuration. Those with (SQL Server Mirror) and (Oracle RAC) are storage-related charges only.
engineCode 43 0 / 10 / 11 / 12 …etc Internal code identifying "engine × edition × license type". Described later
engineMajorVersion 8 10 / 11 / 12 / 13 / 14 / 15 / 5.7 / 8.0 Engine major version. Used in RDS Extended Support pricing records
engineMediaType 2 AWS-provided / Customer-provided Engine media provider. RDS Custom (BYOM) is Customer-provided
enhancedNetworkingSupported 1 Yes Whether enhanced networking is supported
extendedSupportPricingYear 4 Year 1, Year 2 (PP) / Year 1, Year 2 / Year 3 (PP) / Year 3 RDS Extended Support year
group 11 API Request / Aurora Backtrack / Aurora Global Database / Aurora I/O Operation / Provisioned GB / RDS CDC Data Transfer / RDS I/O Operation / RDS Snapshot Export / RDS Zero-ETL Data Transfer / RDS-PIOPS / RDS-Throughput Billing item groups other than instance hours
groupDescription 12 Aurora Backtrack Change Records / Aurora Global Database Replicated IO / Data API requests / Input/Output Operation / Performance Insights API requests / RDS Provisioned GP3 IOPS / RDS Provisioned IO2 IOPS / RDS Provisioned IOPS / RDS Provisioned Throughput / RDS Snapshot Size / RDS Zero-ETL Initial Load and Re-sync Export for Commercial Engines / RDS zero-ETL change data capture records consumed from source RDS engine Description of group
instanceFamily 6 Compute optimized / General purpose / Memory optimized / Micro instances / T3 / T4G Use-case classification of instance family. T3/T4G have inconsistent naming rather than classification names
instanceType 417 db.c6gd.12xlarge / db.c6gd.16xlarge / db.c6gd.2xlarge / db.c6gd.4xlarge …etc DB instance class
instanceTypeFamily 49 C6gd / M1 / M2 / M3 …etc DB instance class type
LeaseContractLength 2 1yr / 3yr RI contract term. Used in records with termType=Reserved
licenseModel 6 Bring your own license / Bring your own media / License included / Marketplace / NA / No license required License type. Required to uniquely narrow down pricing for Oracle/SQL Server/Db2. Bring your own media is for RDS Custom, Marketplace is for Db2 mpl
limitlesspreview 1 Yes Related to Aurora Limitless Database preview?
location 38 AWS GovCloud (US-East) / AWS GovCloud (US-West) / Africa (Cape Town) / Any …etc Region display name
locationType 2 AWS Outposts / AWS Region Whether it's a standard region or Outposts
maxVolumeSize 4 128 TB / 16 TB / 3 TB / 64 TB Maximum storage size
memory 43 0.613 GiB / 1 GiB / 1.7 GiB / 1024 GiB …etc Memory capacity
minVolumeSize 3 10 GB / 100 GB / 5 GB Minimum storage size
networkPerformance 66 10 Gbps / 10 Gigabit / 10,000 Mbps / 100 Gbps …etc Network performance
normalizationSizeFactor 38 0.5 / 1.3 / 10.4 / 1152 …etc Normalization factor used for RI size flexibility calculations. NA for all SQL Server editions and Oracle SE2 (li)
OfferingClass 1 standard RI offering class. EC2 also has convertible but RDS only has standard
operation 53 CreateDBInstance:0002 / CreateDBInstance:0003 / CreateDBInstance:0004 / CreateDBInstance:0005 …etc Billing operation code. The last 4 digits match engineCode
physicalProcessor 21 5th Generation AMD EPYC / AWS Graviton2 / AWS Graviton3 / AWS Graviton4 …etc Physical processor type
processorArchitecture 3 32-bit or 64-bit / 64-bit / x86 Processor architecture
processorFeatures 4 Intel AVX, Intel AVX2, Intel AVX512, Intel Turbo / Intel AVX, Intel AVX2, Intel Turbo / Intel AVX; Intel AVX2; Intel Turbo / Intel AVX; Intel Turbo Processor instruction set extensions. Delimiters are mixed between , and ;
productFamily 13 Aurora Global Database / CPU Credits / Database Instance / Database Storage / Limitless / Performance Insights / Provisioned IOPS / Provisioned Throughput / RDSProxy / ServerlessV2 / Serverless / Storage Snapshot / System Operation Billing target type. Specify Database Instance to retrieve only instance hourly charges
PurchaseOption 3 All Upfront / No Upfront / Partial Upfront RI payment method
regionCode 37 af-south-1 / ap-east-1 / ap-east-2 / ap-northeast-1 …etc Region code
Restriction 1 Limited SKU Usage Only appears in Aurora Data API request billing
servicecode 1 AmazonRDS Service code. Fixed value since already specified with --service-code
servicename 1 Amazon Relational Database Service Service name
storage 47 1 x 118 NVMe SSD / 1 x 120 SSD / 1 x 150 NVMe SSD / 1 x 160 SSD …etc Storage configuration
storageMedia 3 AmazonS3 / Magnetic / SSD Storage media type
termType 2 OnDemand / Reserved Pricing type
unbundledLicensing 2 FALSE / TRUE Whether the license is unbundled
usagetype 25806 AFS1-ASv2-ExtendedSupport:Yr1-Yr2:AuroraPostgreSQL13 / AFS1-Aurora:BackupUsage …etc Usage type appearing in billing details
vcpu 16 0 / 128 / 12 / 16 …etc Number of vCPUs
volumeName 4 gp2 / gp3 / io1 / io2 EBS volume name. Used in storage pricing records
volumeType 9 General Purpose (SSD) / General Purpose-Aurora / General Purpose-GP3 / General Purpose / IO Optimized-Aurora / Magnetic / Provisioned IOPS (SSD) / Provisioned IOPS-IO2 / Provisioned IOPS Volume type display name
windowslicensemultiplier 2 1 / 2 Windows license multiplier

Even narrowed down to Amazon RDS, this many attributes and values exist. While some attributes/values are not related to On-Demand pricing, it's clear that it's not enough to just care about region and DB engine.

And when I previously retrieved Amazon RDS Reserved Instance information, I used aws rds describe-reserved-db-instances-offerings, but I noticed that Reserved Instance offering information can also be retrieved with aws pricing as well. (For now, I'll continue to retrieve only On-Demand pricing in this post.)

Drilling Down into the List of engineCode Values

The enginecode attribute values caught my attention, so let me check the full list. The last line displays them side by side.

aws pricing get-attribute-values \
  --service-code AmazonRDS \
  --attribute-name engineCode \
  --region us-east-1 \
  --output json \
| jq -r '.AttributeValues[].Value' | sort -n | paste -sd' ' -
0 2 3 4 5 6 8 9 10 11 12 14 15 16 18 19 20 21 27 28 29 34 35 50 51 52 53 210 220 230 231 232 240 241 401 402 403 405 406 407 410 411 420

There are all sorts of codes.

Let me check the DB engine, DB edition, license model, and media type for each code.

aws pricing get-attribute-values \
  --service-code AmazonRDS \
  --attribute-name engineCode \
  --region us-east-1 \
  --output json \
| jq -r '.AttributeValues[].Value' | sort -n | while IFS= read -r c; do
  aws pricing get-products \
    --service-code AmazonRDS \
    --region us-east-1 \
    --output json \
    --filters "Type=TERM_MATCH,Field=engineCode,Value=$c" \
              "Type=TERM_MATCH,Field=productFamily,Value=Database Instance" \
  | jq -r --arg c "$c" '
      [.PriceList[] | fromjson | .product.attributes] as $r
      | def u(f): ($r | map(f // "-") | unique | join(" / ")) | if . == "" then "-" else . end;
        [$c, ($r | length | tostring),
         u(.databaseEngine), u(.databaseEdition), u(.licenseModel),
         u(.engineMediaType), u(.deploymentModel)]
      | @tsv'
done | { printf 'code\tCount\tengine\tedition\tlicense\tmedia\tdeployModel\n'; cat; } | column -t -s$'\t'

(Since this targets tens of thousands of records without filtering by region, it took over 10 minutes to complete.)

Here's a summary based on the results.

code databaseEngine databaseEdition licenseModel engineMediaType Notes
0 Any — — — Used for storage charges etc.
2 MySQL — No license required —
3 Oracle Standard One — — Storage charges only. Discontinued edition
4 Oracle Standard Bring your own license — Discontinued edition. Only 1 record remains in ap-south-1
5 Oracle Enterprise Bring your own license — / Customer-provided Only db.r5b series has Customer-provided
6 Oracle Standard One — — Storage charges only. Discontinued edition
8 SQL Server Standard — — Storage/PIOPS charges only
9 SQL Server Enterprise — — Storage/PIOPS charges only
10 SQL Server Express License included —
11 SQL Server Web License included —
12 SQL Server Standard License included —
14 PostgreSQL — No license required —
15 SQL Server Enterprise License included —
16 Aurora MySQL — No license required —
18 MariaDB — No license required —
19 Oracle Standard Two Bring your own license —
20 Oracle Standard Two License included —
21 Aurora PostgreSQL — No license required —
27 Db2 Community Bring your own license —
28 Db2 Standard Bring your own license —
29 Db2 Advanced Bring your own license —
34 Db2 Standard Marketplace —
35 Db2 Advanced Marketplace —
50 SQL Server Standard Developer Bring your own media —
51 SQL Server Enterprise Developer Bring your own media —
52 SQL Server Standard Bring your own media Customer-provided
53 SQL Server Enterprise Bring your own media Customer-provided
210 MySQL (on-premise for Outpost) — No license required — Outposts
220 PostgreSQL (on-premise for Outpost) — No license required — Outposts
230 SQL Server (on-premise for Outpost) Enterprise License included — Outposts
231 SQL Server (on-premise for Outpost) Standard License included — Outposts
232 SQL Server (on-premise for Outpost) Web License included — Outposts
240 Oracle (on-premises for Outposts) Enterprise Bring your own license — Outposts
241 Oracle (on-premises for Outposts) Standard Two Bring your own license — Outposts
401 SQL Server Web NA AWS-provided RDS Custom
402 SQL Server Standard NA AWS-provided RDS Custom
403 SQL Server Enterprise NA AWS-provided RDS Custom
405 SQL Server Standard NA Customer-provided RDS Custom
406 SQL Server Enterprise NA Customer-provided RDS Custom
407 SQL Server Developer NA Customer-provided RDS Custom
410 Oracle Enterprise Bring your own license Customer-provided RDS Custom
411 Oracle Standard Two Bring your own license Customer-provided RDS Custom

I feel like shaking hands with the DB engines that have only one corresponding code (MySQL, PostgreSQL, Aurora MySQL, MariaDB, Aurora PostgreSQL). On the other hand, I'd prefer to keep some distance from SQL Server, Oracle, and Db2, which have many variations.

Getting Amazon RDS On-Demand Pricing

Based on the list of attributes we've reviewed so far, we'll use get-products to retrieve on-demand pricing.

For this exercise, we want to retrieve information under the following conditions:

  • Retrieve a list of on-demand pricing for a specific DB engine in a specific region
  • The retrieved information should consist of the following columns:
    • DB instance class
    • Whether Multi-AZ or not
    • On-demand price

Here is an example command to retrieve on-demand pricing for MySQL in the Tokyo region:

REGION=ap-northeast-1
DBENGINE=MySQL
TS=$(date +%Y%m%d_%H%M%S)

OD_CSV="rds_ondemand_${DBENGINE}_${REGION}_${TS}.csv"

aws pricing get-products \
  --service-code AmazonRDS \
  --region us-east-1 \
  --output json \
  --filters "Type=TERM_MATCH,Field=regionCode,Value=$REGION" \
            "Type=TERM_MATCH,Field=databaseEngine,Value=$DBENGINE" \
            "Type=TERM_MATCH,Field=productFamily,Value=Database Instance" \
| jq -r '
  .PriceList[] | fromjson
  | .product.attributes as $a
  | select($a.deploymentOption=="Single-AZ" or $a.deploymentOption=="Multi-AZ")
  | (.terms.OnDemand // {})[]?.priceDimensions[]?
  | select(.unit=="Hrs")
  | [$a.instanceType, $a.deploymentOption, .pricePerUnit.USD]
  | @csv
' | sort -t, -k1,1 -k2,2 > /tmp/ondemand_body.csv

{ echo '"DBInstanceClass","MultiAZ","OnDemand"'; cat /tmp/ondemand_body.csv; } > "$OD_CSV"

echo "$OD_CSV"

Running this will output the CSV filename as shown below:

Execution example
rds_ondemand_MySQL_ap-northeast-1_20260929_014420.csv

The contents of the CSV file will list the DB instance class, Multi-AZ status, and on-demand price as shown below:

% head -n 15  rds_ondemand_MySQL_ap-northeast-1_20260929_014306.csv
"DBInstanceClass","MultiAZ","OnDemand"
"db.m1.large","Multi-AZ","0.5800000000"
"db.m1.large","Single-AZ","0.2900000000"
"db.m1.medium","Multi-AZ","0.2900000000"
"db.m1.medium","Single-AZ","0.1450000000"
"db.m1.small","Multi-AZ","0.1500000000"
"db.m1.small","Single-AZ","0.0750000000"
"db.m1.xlarge","Multi-AZ","1.1700000000"
"db.m1.xlarge","Single-AZ","0.5850000000"
"db.m2.2xlarge","Multi-AZ","1.4800000000"
"db.m2.2xlarge","Single-AZ","0.7400000000"
"db.m2.4xlarge","Multi-AZ","2.9500000000"
"db.m2.4xlarge","Single-AZ","1.4750000000"
"db.m2.xlarge","Multi-AZ","0.7300000000"
"db.m2.xlarge","Single-AZ","0.3650000000"

After that, you can retrieve the on-demand pricing list for other engines by simply changing the variables at the top — and that would be that.

……Or so we thought.

Things to Consider Per DB Engine

With regard to what we're trying to accomplish here, there are attributes that need to be considered for each DB engine. The attributes marked with ● in the table below require specification or filtering.

DB Engine engine edition license storage depModel depOption
MySQL ● ●
PostgreSQL ● ●
MariaDB ●
Aurora MySQL ● ●
Aurora PostgreSQL ● ●
Oracle ● ● ● ●
SQL Server ● ● ● ●
Db2 ● ● ●

The column names in the table represent the following:

Column Name Attribute Description
engine databaseEngine Required for all engines
edition databaseEdition Required for engines that have editions
license licenseModel Required for engines that have license models (such as BYOL)
storage storage For Aurora, specification of whether Aurora IO Optimization Mode is used or not is required
depModel deploymentModel Specification of whether Custom is used or not is required
depOption deploymentOption Filtering is required when limiting to only Single-AZ/Multi-AZ

While adding more columns to the retrieved information would eliminate the need for filtering, since we want to match the format of the RDS Reserved Instance pricing information obtained in the previous blog, we'll handle this carefully.

For example, regarding storage, Aurora has a mode called Aurora IO Optimization Mode, which results in higher on-demand pricing compared to the standard mode.

Example with 4 columns
"DBInstanceClass","MultiAZ","OnDemand","Storage"
"db.r5.12xlarge","Single-AZ","10.9200000000","Aurora IO Optimization Mode"
"db.r5.12xlarge","Single-AZ","8.4000000000","EBS Only"

Without filtering and without including the storage value as a column, it's impossible to tell which mode's on-demand price was retrieved. Therefore, if we don't want to add more columns (keeping it at 3 columns), filtering is necessary.

Regarding deploymentOption, for MySQL and PostgreSQL, in addition to Single-AZ/Multi-AZ, the value Multi-AZ (readable standbys) is also possible. This represents a Multi-AZ DB cluster. Filtering is required if this becomes noise.

Server-Side Filtering and Client-Side Filtering

In the commands used here, the --filters option can be used for server-side filtering, and jq's select can be used for client-side filtering.

It is generally better to filter on the server side as it reduces the amount of data handled locally. The following types can be used with --filters:

Type Behavior
TERM_MATCH Returns only items where the specified attribute matches the specified value
EQUALS Returns items with values that exactly match the specified value
CONTAINS Returns items with values that contain the specified value as a substring
ANY_OF Returns items with values that match any of the specified multiple values
NONE_OF Returns items with values that do not match any of the specified multiple values

An example of filtering that is as generic as possible regardless of the DB engine might look like this. (Of course, the conditions will differ depending on what values you want to retrieve.)

REGION=ap-northeast-1
DBENGINE=MySQL

aws pricing get-products \
  --service-code AmazonRDS \
  --region us-east-1 \
  --output json \
  --filters "$(jq -cn --arg region "$REGION" --arg engine "$DBENGINE" '[
      {Type:"TERM_MATCH", Field:"regionCode",       Value:$region},
      {Type:"TERM_MATCH", Field:"databaseEngine",   Value:$engine},
      {Type:"TERM_MATCH", Field:"productFamily",    Value:"Database Instance"},
      {Type:"ANY_OF",     Field:"deploymentOption", Value:"Single-AZ,Multi-AZ"},
      {Type:"NONE_OF",    Field:"storage",          Value:"Aurora IO Optimization Mode"},
      {Type:"NONE_OF",    Field:"deploymentModel",  Value:"Custom"}
    ]')" \
| jq -r '
  .PriceList[] | fromjson
  ……

If writing --filters in JSON syntax seems a bit verbose, you can use the shorthand syntax and leave the filtering to the jq side. Choose whichever is clearer. (Note that the shorthand syntax does not support values containing commas, such as Single-AZ,Multi-AZ.)

……
  --filters "Type=TERM_MATCH,Field=regionCode,Value=$REGION" \
            "Type=TERM_MATCH,Field=databaseEngine,Value=$DBENGINE" \
            "Type=TERM_MATCH,Field=productFamily,Value=Database Instance" \
| jq -r '
  .PriceList[] | fromjson
  | .product.attributes as $a
  | select($a.deploymentOption=="Single-AZ" or $a.deploymentOption=="Multi-AZ")
  | select($a.storage != "Aurora IO Optimization Mode")
  | select(($a.deploymentModel // "") != "Custom")
……

When Dealing with Editions and Licenses, Using engineCode Is Easier

I mentioned "an example of filtering that is as generic as possible regardless of the DB engine" and listed several examples above, but when considering Oracle, SQL Server, and Db2 as targets, filtering by databaseEdition and licenseModel is also necessary.

To avoid writing complex conditional branches while still wanting a command that works for any DB engine just by changing environment variables, we'll use engineCode as the filtering condition.

That said, there are many types of engineCode as we explored earlier, so this time we'll limit it to the types that can be confirmed in the Amazon RDS Reserved Instance offerings.

Engine type list retrieved from a previous blog (Tokyo region)
% aws rds describe-reserved-db-instances-offerings \
  --region ap-northeast-1 \
  --output json \
| jq -r '[.ReservedDBInstancesOfferings[].ProductDescription] | unique[]'
aurora-mysql
aurora-postgresql
custom-sqlserver-ee(byol)
custom-sqlserver-ee(li)
custom-sqlserver-se(byol)
custom-sqlserver-se(li)
custom-sqlserver-web(li)
db2-ae(byol)
db2-ae(mpl)
db2-se(byol)
db2-se(mpl)
mariadb
mysql
oracle-ee(byol)
oracle-se2 (byol)
oracle-se2(li)
postgresql
sqlserver-ee(li)
sqlserver-ex(li)
sqlserver-se(li)
sqlserver-web(li)

We'll pre-map the engine names above (technically ProductDescription) to their corresponding engineCode values, and use a method of specifying the engine name to filter.

REGION=ap-northeast-1
ENGINE="oracle-ee(byol)"
TS=$(date +%Y%m%d_%H%M%S)

engine_code() {
  case "$(echo "$1" | tr -d ' ')" in
    "mysql")                     echo 2 ;;
    "postgresql")                echo 14 ;;
    "mariadb")                   echo 18 ;;
    "aurora-mysql")              echo 16 ;;
    "aurora-postgresql")         echo 21 ;;
    "oracle-ee(byol)")           echo 5 ;;
    "oracle-se2(byol)")          echo 19 ;;
    "oracle-se2(li)")            echo 20 ;;
    "sqlserver-ee(li)")          echo 15 ;;
    "sqlserver-se(li)")          echo 12 ;;
    "sqlserver-web(li)")         echo 11 ;;
    "sqlserver-ex(li)")          echo 10 ;;
    "db2-ae(byol)")              echo 29 ;;
    "db2-ae(mpl)")               echo 35 ;;
    "db2-se(byol)")              echo 28 ;;
    "db2-se(mpl)")               echo 34 ;;
    "custom-sqlserver-ee(byol)") echo 406 ;;
    "custom-sqlserver-ee(li)")   echo 403 ;;
    "custom-sqlserver-se(byol)") echo 405 ;;
    "custom-sqlserver-se(li)")   echo 402 ;;
    "custom-sqlserver-web(li)")  echo 401 ;;
    *) echo "" ;;
  esac
}

ENGINE_CODE=$(engine_code "$ENGINE")
ENGINE_SLUG=$(echo "$ENGINE" | sed -E 's/[[:space:]]+//g; s/[()]/_/g; s/_+$//')

OD_CSV="rds_ondemand_${ENGINE_SLUG}_${REGION}_${TS}.csv"

aws pricing get-products \
  --service-code AmazonRDS \
  --region us-east-1 \
  --output json \
  --filters "Type=TERM_MATCH,Field=regionCode,Value=$REGION" \
            "Type=TERM_MATCH,Field=engineCode,Value=$ENGINE_CODE" \
            "Type=TERM_MATCH,Field=productFamily,Value=Database Instance" \
| jq -r '
  .PriceList[] | fromjson
  | .product.attributes as $a
  | select($a.deploymentOption=="Single-AZ" or $a.deploymentOption=="Multi-AZ")
  | select($a.storage != "Aurora IO Optimization Mode")
  | (.terms.OnDemand // {})[]?.priceDimensions[]?
  | select(.unit=="Hrs")
  | [$a.instanceType, $a.deploymentOption, .pricePerUnit.USD]
  | @csv
' | sort -t, -k1,1 -k2,2 > /tmp/ondemand_body.csv

{ echo '"DBInstanceClass","MultiAZ","OnDemand"'; cat /tmp/ondemand_body.csv; } > "$OD_CSV"

echo "$OD_CSV"

Here is what the execution looks like. The CSV filename is displayed.

rds_ondemand_oracle-ee_byol_ap-northeast-1_20260929_145201.csv

The contents look like this. We were able to retrieve on-demand pricing while maintaining 3 columns and outputting separate files per edition and license.

% head -n 15 rds_ondemand_oracle-ee_byol_ap-northeast-1_20260929_145201.csv
"DBInstanceClass","MultiAZ","OnDemand"
"db.m3.2xlarge","Multi-AZ","1.9300000000"
"db.m3.2xlarge","Single-AZ","0.9650000000"
"db.m3.large","Multi-AZ","0.4800000000"
"db.m3.large","Single-AZ","0.2400000000"
"db.m3.medium","Multi-AZ","0.2400000000"
"db.m3.medium","Single-AZ","0.1200000000"
"db.m3.xlarge","Multi-AZ","0.9700000000"
"db.m3.xlarge","Single-AZ","0.4850000000"
"db.m4.10xlarge","Multi-AZ","10.1740000000"
"db.m4.10xlarge","Single-AZ","5.0870000000"
"db.m4.16xlarge","Multi-AZ","16.2816000000"
"db.m4.16xlarge","Single-AZ","8.1408000000"
"db.m4.2xlarge","Multi-AZ","2.0340000000"
"db.m4.2xlarge","Single-AZ","1.0170000000"

Summary (Reprinted)

  • You can retrieve Amazon RDS on-demand pricing using aws pricing get-attribute-values
    • Reserved Instance pricing can also be retrieved (not covered in this blog)
  • There are several attributes to consider when running aws pricing get-attribute-values. These attributes differ depending on the DB engine.

Closing

This was a write-up about retrieving Amazon RDS on-demand pricing using the AWS Price List Query API.

I found that the attributes to consider differ by DB engine, and that some ingenuity is required when trying to retrieve information generically. I started with the light intention of "just wanting to get all on-demand prices at once," but ended up feeling like I had glimpsed into the abyss.

I hope this is useful for those who venture into the abyss.

That's all from チバユキ (@batchicchi).

References

Share this article

AWSのお困り事はクラスメソッドへ