RDS RIのリソースレベル適用状況をCUR2.0から把握する by DuckDB
RDSのリザーブドインスタンス(RI)が 「どのDBインスタンスに適用されているのか」
を "長期的" に把握したいです。
要件を満たすために Cost and Usage Report (CUR) 2.0 と DuckDB を使った分析 を本ブログでトライしてみます。
DuckDBはオンライン分析処理(OLAP)に特化したデータベースシステムです。
なお、短期的な分析(過去14日間)や 非リソースレベルの分析であれば、 Cost Explorer による リソースレベルの可視化 や RI使用率、カバレッジレポート で済みます。 CURより簡単に確認できます。
前提
CUR2.0のデータエクスポート設定が完了していることが前提です。 私が関わっている環境では以下のような設定でS3バケットに出力しています。
設定項目 | 値 |
---|---|
基本 > エクスポートタイプ | 標準データエクスポート |
データテーブルコンテンツ | CUR 2.0 |
コンテンツ: リソースIDを含める | Yes |
コンテンツ: コスト配分データを分割 | No |
コンテンツ: 時間粒度 | 日次 |
コンテンツ: 列選択 | 全て選択 |
配信: 圧縮タイプ | Parquet |
配信: ファイルのバージョニング | 既存のデータエクスポートファイルを上書き |
また、分析はローカルの端末で実施します。 実行環境は以下のとおりです。
- MacBook Pro (Apple M1 Max)
- macOS Sequoia 15.3
- DuckDB v1.2.0
セットアップ
DuckDBのインストール
以下ガイドのとおりに、 DuckDBをローカルにインストールします。
brew install duckdb
duckdb --version
# v1.2.0 5f5512b827
duckdb
# v1.2.0 5f5512b827
# Enter ".help" for usage hints.
# Connected to a transient in-memory database.
# Use ".open FILENAME" to reopen on a persistent database.
# D
CURオブジェクトのダウンロード
S3に保存されているCURオブジェクトを ローカルにダウンロードします。 今回は 2025/02 途中のCURオブジェクトを 分析対象とします。
### 適宜変更ください
bucket="EXAMPLE-DOC-BUCKET"
prefix="cur2"
export_name="daily-export"
### ファイルの確認
aws s3 ls ${bucket}/${prefix}/${export_name}/data/BILLING_PERIOD=2025-02/
# 2025-02-14 07:37:06 60149849 daily-export-00001.snappy.parquet
# 2025-02-14 07:37:07 60158854 daily-export-00002.snappy.parquet
# 2025-02-14 07:37:07 60391747 daily-export-00003.snappy.parquet
### ファイルのダウンロード
aws s3 cp s3://${bucket}/${prefix}/${export_name}/data/BILLING_PERIOD=2025-02/ . --recursive
ls
# daily-export-00001.snappy.parquet
# daily-export-00002.snappy.parquet
# daily-export-00003.snappy.parquet
クエリ確認
parquet ファイルを確認できるかチェックします。
duckdb
起動後、 SELECT * FROM 'daily-export-*.snappy.parquet' LIMIT 10;
を実行してみます。 以下のような出力となりました。
以降、RDS RIのリソースレベル適用状況を把握するために 試したクエリを紹介していきます。
(その前に) オンデマンドインスタンスとリザーブドインスタンスのコストについて
RDS DBインスタンスの時間単位コストを求めるには、 まずは prodcut/InstanceType の値を db:*
で絞るとよいです (+ 加えて lineItem/UsageType も *Usage:db.*
あたりで指定すると、より確実です)。 db.t3.small
や db.m5.large
など、「インスタンスタイプの使用コスト」の項目を取得できます。
その明細が「オンデマンド」か「リザーブド」かは lineItem/LineItemType や pricing/term から判断できます。
lineItem/LineItemType | pricing/term | |
---|---|---|
オンデマンドインスタンスの場合 | Usage |
OnDemand |
リザーブドインスタンスの場合 | DiscountedUsage |
Reserved |
オンデマンドインスタンスのコストは、通常は lineItem/UnblendedCost を見ます。 リザーブドインスタンスの場合は UnblendedCost は 0 です。
リーザーブドインスタンスの節約額を見積もるには reservation/EffectiveCost と pricing/publicOnDemandCost を見るとよいでしょう。 EffectiveCost が実質的なコスト、 publicOnDemandCost が「もしオンデマンドで起動していた場合のコスト」です。 2つの差分が節約額です。
画像引用: AWS Cost and Usage Reports【AWS Black Belt】 - YouTube
オンデマンド料金が適用されているRDSインスタンス
以下クエリを実行することで、 「オンデマンド料金が適用されているRDSインスタンスの一覧」 を取得できます。
-- ファイル名や期間は適宜変更ください
SELECT
line_item_usage_account_name as AWSAccount,
regexp_replace(line_item_resource_id, '^arn:aws:.+:\d+:', '') as Resource,
product_instance_type as InstanceType,
CAST(SUM(line_item_unblended_cost) as INTEGER) as OnDemandCost
FROM './daily-export-*.parquet'
WHERE
pricing_term = 'OnDemand'
AND product_instance_type LIKE 'db.%'
AND line_item_usage_type LIKE '%Usage:db.%'
AND line_item_usage_start_date BETWEEN DATE '2025-02-03' AND DATE '2025-02-09'
GROUP BY AWSAccount, Resource, InstanceType
HAVING CAST(SUM(line_item_unblended_cost) as INTEGER) > 0
ORDER BY AWSAccount ASC, OnDemandCost DESC;
出力サンプル
出力している列の情報は以下のとおりです。
列名 | 説明や補足 |
---|---|
AWSAccount | AWSアカウント名 |
Resource | DBインスタンスのリソースID(ARNの末尾部分のみ) |
InstanceType | DBインスタンスのインスタンスタイプ |
OnDemandCost | 対象期間中のオンデマンドコスト合計 |
リザーブドインスタンス一覧とその実質コスト
以下クエリを実行することで、 「リザーブドインスタンス一覧とその実質コスト」 を取得できます。
-- ファイル名や期間は適宜変更ください
SELECT
line_item_usage_account_name as AWSAccount,
regexp_replace(line_item_resource_id, '^arn:aws:.+:\d+:', '') as Resource,
product_instance_type as InstanceType,
CAST(SUM(reservation_effective_cost) as INTEGER) as EffectiveCost
FROM './daily-export-*.parquet'
WHERE
pricing_term = 'Reserved'
AND product_instance_type LIKE 'db.%'
AND line_item_usage_type LIKE '%Usage:db.%'
AND line_item_usage_start_date BETWEEN DATE '2025-02-03' AND DATE '2025-02-09'
GROUP BY AWSAccount, Resource, InstanceType
HAVING CAST(SUM(reservation_effective_cost) as INTEGER) > 0
ORDER BY AWSAccount ASC, EffectiveCost DESC;
出力サンプル
出力している列の情報は以下のとおりです。
列名 | 説明や補足 |
---|---|
AWSAccount | AWSアカウント名 |
Resource | DBインスタンスのリソースID(ARNの末尾部分のみ) |
InstanceType | DBインスタンスのインスタンスタイプ |
EffectiveCost | 対象期間中のリザーブドインスタンスによる実質コスト |
これまでの分析を1テーブルに集約 + 節約額を表示
最後に これまでのクエリを1つにまとめて、 「リザーブドインスタンスによる節約額」の情報を付与したテーブルを作成します。
-- ファイル名や期間は適宜変更ください
SELECT
line_item_usage_account_name as AWSAccount,
regexp_replace(line_item_resource_id, '^arn:aws:.+:\d+:', '') as Resource,
product_instance_type as InstanceType,
pricing_term as PricingTerm,
CAST(SUM(line_item_unblended_cost) as INTEGER) +
CAST(SUM(reservation_effective_cost) as INTEGER) as Cost,
CAST(SUM(pricing_public_on_demand_cost) as INTEGER) -
CAST(SUM(line_item_unblended_cost) as INTEGER) -
CAST(SUM(reservation_effective_cost) as INTEGER) as Saving
FROM './daily-export-*.parquet'
WHERE
product_instance_type LIKE 'db.%'
AND line_item_usage_type LIKE '%Usage:db.%'
AND line_item_usage_start_date BETWEEN DATE '2025-02-03' AND DATE '2025-02-09'
GROUP BY AWSAccount, Resource, InstanceType, PricingTerm
HAVING
CAST(SUM(line_item_unblended_cost) as INTEGER) > 0
OR CAST(SUM(reservation_effective_cost) as INTEGER) > 0
ORDER BY AWSAccount ASC, Resource ASC;
出力サンプル
出力している列の情報は以下のとおりです。
列名 | 説明や補足 |
---|---|
AWSAccount | AWSアカウント名 |
Resource | DBインスタンスのリソースID(ARNの末尾部分のみ) |
InstanceType | DBインスタンスのインスタンスタイプ |
PricingTerm | OnDemand もしくは Reserved |
Cost | OnDemand の場合: 非ブレンドコストReserved の場合: 実質コスト |
Saving | OnDemand の場合: 0Reserved の場合: リザーブドインスタンスによる節約額 |
おわりに
RIの適用状況を把握するために CUR2.0 と DuckDB を活用してみました。 長期的なリソースレベル情報が欲しいときには CUR が役に立ちます… が、各列や各行の読み込みがやはり大変です。
基本的には CUR 以外のコストツール(Cost Explorer等)を活用して、 それらでは難しい分析においてのみ CUR を使っていくのが良いと感じました。
あとDuckDBは非常に便利です。 手元でサクッとログを分析したい場合に、今後活用していこうと思います。
以上、参考になれば幸いです。