はじめに
metaps Advent Calendar 2025 3日目の記事です。
$\color{red}{\huge \textsf{大遅刻しました... 申し訳ありません!!!!}}$
この記事は、2025/12に開催された第2木曜LT会の発表をもとに作成しています。
3行解説
- Data Exports で Cost And Usage Report 2.0 (以降CUR2.0と呼称) データをS3に出力
- Athena で CUR2.0 のデータをクエリする
- 購入した Savings Plans (以降SP) のアカウントごとのコミットメント量が見れるよ
実際の手順
Data Exportsの作成
設定は以下の通り
- 標準データエクスポート
- テーブルデータコンテンツは CUR2.0
- 時間粒度は [時間単位]
- SP は日時ではなく時間単位でのコミットのため、計算する際に整合性が取りやすい
- ファイル形式は Parquet
- ファイルのバージョニングは [既存のデータエクスポートファイルを上書き]
- [新しいデータエクスポートファイルを作成] は毎時ファイルを差分で出します。S3バケットを圧迫するため非推奨
- ストレージオプションは [新しい S3 バケットを作成して使用する] を使うと自動的にバケットポリシーが割り当てられるのでおすすめ
- あと他のファイルが混ざらない点も○
仮にバケットを別で作成したいって時はこのバケットポリシーを書いてください。
{} のところは各自でパラメータを変更してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnableAWSDataExportsToWriteToS3AndCheckPolicy",
"Effect": "Allow",
"Principal": {
"Service": [
"billingreports.amazonaws.com",
"bcm-data-exports.amazonaws.com"
]
},
"Action": [
"s3:PutObject",
"s3:GetBucketPolicy"
],
"Resource": [
"arn:aws:s3:::{BucketName}",
"arn:aws:s3:::{BucketName}/*"
],
"Condition": {
"StringLike": {
"aws:SourceAccount": "{AccountId}",
"aws:SourceArn": [
"arn:aws:cur:{Legion}:{AccountId}:definition/*",
"arn:aws:bcm-data-exports:{Legion}:{AccountId}:export/*"
]
}
}
}
]
}
Glue Data Catalog Database の作成
特に話すことがないので省略
Glue Data Catalog Table の作成
コンソールからやってもいいですが、クエリ定義ぽちぽちやるのがめんどくさいので 今回は Athena から CREATE EXTERNAL TABLE を実行します。
{} のところは各自でパラメータを変更してください。
補足: 1年間のパーティショニングがかかっています。期間広げたい方はprojection.billing_period.range を変更してください。
CREATE EXTERNAL TABLE cur (
bill_bill_type string,
bill_billing_entity string,
bill_billing_period_end_date timestamp,
bill_billing_period_start_date timestamp,
bill_invoice_id string,
bill_invoicing_entity string,
bill_payer_account_id string,
bill_payer_account_name string,
cost_category map<string,string>,
discount map<string,string>,
discount_bundled_discount string,
discount_total_discount string,
identity_line_item_id string,
identity_time_interval string,
line_item_availability_zone string,
line_item_blended_cost double,
line_item_blended_rate string,
line_item_currency_code string,
line_item_legal_entity string,
line_item_line_item_description string,
line_item_line_item_type string,
line_item_net_unblended_cost string,
line_item_net_unblended_rate string,
line_item_normalization_factor double,
line_item_normalized_usage_amount double,
line_item_operation string,
line_item_product_code string,
line_item_resource_id string,
line_item_tax_type string,
line_item_unblended_cost double,
line_item_unblended_rate string,
line_item_usage_account_id string,
line_item_usage_account_name string,
line_item_usage_amount double,
line_item_usage_end_date timestamp,
line_item_usage_start_date timestamp,
line_item_usage_type string,
pricing_currency string,
pricing_lease_contract_length string,
pricing_offering_class string,
pricing_public_on_demand_cost double,
pricing_public_on_demand_rate string,
pricing_purchase_option string,
pricing_rate_code string,
pricing_rate_id string,
pricing_term string,
pricing_unit string,
product map<string,string>,
product_comment string,
product_fee_code string,
product_fee_description string,
product_from_location string,
product_from_location_type string,
product_from_region_code string,
product_instance_family string,
product_instance_type string,
product_instancesku string,
product_location string,
product_location_type string,
product_operation string,
product_pricing_unit string,
product_product_family string,
product_region_code string,
product_servicecode string,
product_sku string,
product_to_location string,
product_to_location_type string,
product_to_region_code string,
product_usagetype string,
reservation_amortized_upfront_cost_for_usage double,
reservation_amortized_upfront_fee_for_billing_period double,
reservation_availability_zone string,
reservation_effective_cost double,
reservation_end_time string,
reservation_modification_status string,
reservation_net_amortized_upfront_cost_for_usage string,
reservation_net_amortized_upfront_fee_for_billing_period string,
reservation_net_effective_cost string,
reservation_net_recurring_fee_for_usage string,
reservation_net_unused_amortized_upfront_fee_for_billing_period string,
reservation_net_unused_recurring_fee string,
reservation_net_upfront_value string,
reservation_normalized_units_per_reservation string,
reservation_number_of_reservations string,
reservation_recurring_fee_for_usage double,
reservation_reservation_a_r_n string,
reservation_start_time string,
reservation_subscription_id string,
reservation_total_reserved_normalized_units string,
reservation_total_reserved_units string,
reservation_units_per_reservation string,
reservation_unused_amortized_upfront_fee_for_billing_period double,
reservation_unused_normalized_unit_quantity double,
reservation_unused_quantity double,
reservation_unused_recurring_fee double,
reservation_upfront_value double,
resource_tags map<string,string>,
savings_plan_amortized_upfront_commitment_for_billing_period double,
savings_plan_end_time string,
savings_plan_instance_type_family string,
savings_plan_net_amortized_upfront_commitment_for_billing_period double,
savings_plan_net_recurring_commitment_for_billing_period double,
savings_plan_net_savings_plan_effective_cost double,
savings_plan_offering_type string,
savings_plan_payment_option string,
savings_plan_purchase_term string,
savings_plan_recurring_commitment_for_billing_period double,
savings_plan_region string,
savings_plan_savings_plan_a_r_n string,
savings_plan_savings_plan_effective_cost double,
savings_plan_savings_plan_rate double,
savings_plan_start_time string,
savings_plan_total_commitment_to_date double,
savings_plan_used_commitment double
)
PARTITIONED BY (billing_period string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
WITH SERDEPROPERTIES ('parquet.compression' = 'SNAPPY')
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION 's3://{bucket_name}/{data_export_prefix}/{data_export_name}/data'
TBLPROPERTIES (
'serialization.format' = '1',
'projection.enabled' = 'true',
'projection.billing_period.type' = 'date',
'projection.billing_period.range' = 'NOW-1YEAR,NOW',
'projection.billing_period.format' = 'yyyy-MM',
'projection.billing_period.interval' = '1',
'projection.billing_period.interval.unit' = 'MONTHS',
'storage.location.template' = 's3://{bucket_name}/{data_export_prefix}/{data_export_name}/data/BILLING_PERIOD=${billing_period}'
);
Athena クエリ実行
0.01%程度の誤差が発生する可能性があります。あらかじめご了承ください。
yyyy-MM-dd は測りたい日にちを選択してください。
WITH total_commitment AS (
SELECT SUM(savings_plan_used_commitment) AS total_used
FROM cur
WHERE
line_item_usage_start_date >= DATE 'yyyy-MM-dd'
AND line_item_usage_start_date < DATE 'yyyy-MM-dd'
AND line_item_line_item_type = 'SavingsPlanRecurringFee'
),
account_usage AS (
SELECT
line_item_usage_account_name AS account_name,
SUM(savings_plan_savings_plan_effective_cost) AS sp_usage
FROM cur
WHERE
line_item_usage_start_date >= DATE 'yyyy-MM-dd'
AND line_item_usage_start_date < DATE 'yyyy-MM-dd'
AND line_item_line_item_type = 'SavingsPlanCoveredUsage'
GROUP BY line_item_usage_account_name
)
SELECT
a.account_name,
a.sp_usage,
t.total_used AS total_commitment,
ROUND(a.sp_usage / t.total_used * 100, 2) AS usage_percentage
FROM account_usage a
CROSS JOIN total_commitment t
ORDER BY a.sp_usage DESC;
実行結果のカラム説明
-
account_name: 購入済み SP に紐づく AWS アカウント -
sp_usage: 購入済みSP によって割り引かれたオンデマンド料金 -
total_commitment: 日にち別の購入済み SP コミットメント -
usage_percentage:total_commitmentをアカウント別にどのくらい使っているかの割合
注意点
- 当然ですが、SPを支払ったアカウントでないと上記のクエリは実行しても値が出ません。
- 子アカウントで実行する場合は注意してください。
最後に
この結果をもとに Savings Plans Purchase Analyzer と比較して来年以降の SP 購入金額を最適化しましょう!