クラスメソッドメンバーズ提供のCURをサクッとコスト分析!(with DuckDB)

クラスメソッドメンバーズ提供のCURをサクッとコスト分析!(with DuckDB)

Clock Icon2025.03.25

弊社のAWS総合支援サービスである「クラスメソッドメンバーズ」の機能として、 メンバーズCUR を提供しています。 これはAWSで利用されたサービスのコスト/使用量についてのレポート (CUR: Cost and Usage Reports)を取得できるものです。

事前にS3バケットを準備して CURエクスポート設定を作成することで、 継続的なCURのS3出力を実現できます。 詳細や具体的な手順は以下ドキュメントに記載があります。

https://guide.members.classmethod.net/mcur

今回は出力されたメンバーズCURをDuckDBでローカルでサクッと分析してみます。 DuckDBはオンライン分析処理(OLAP)に特化したデータベースシステムです。

なお、大規模な分析や、継続的なダッシュボード運用を実現していく際は、 DuckDBよりもAthenaが向いているケースがあります。 以下ブログが参考になるますのでぜひ御覧ください。

https://dev.classmethod.jp/articles/how-to-create-athena-analysis-environment-with-mcur/
https://dev.classmethod.jp/articles/how-to-create-cid-with-mcur/

セットアップ

クラスメソッドメンバーズ提供のCUR

メンバーズユーザーガイドに沿ってメンバーズCURを設定します。 保存フォーマットはParquet(分析用途に向いている形式)としました。

以下のようにS3バケットに保管されていきます。

aws s3 ls EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/
# 2025-03-24 08:37:33  10961234 MCURExport-00001.snappy.parquet
# 2025-03-24 08:37:33  10891234 MCURExport-00002.snappy.parquet
# 2025-03-24 08:37:33  11011234 MCURExport-00003.snappy.parquet
# 2025-03-24 08:37:33  10771234 MCURExport-00004.snappy.parquet
# 2025-03-24 08:37:33  10931234 MCURExport-00005.snappy.parquet
# ↑ プレフィクスが mcur 、 エクスポート名が MCURExport の場合

aws s3 ls EXAMPLE-DOC-BUCKET/mcur/MCURExport/20250301-20250401/
# 2025-03-24 08:37:57      29396 MCURExport-Manifest.json
# ↑ これはメタデータファイル(スキーマ情報など)

これら parquetファイルを事前にローカルに落としておきます。

aws s3 cp s3://EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/ . --recursive --include "*.snappy.parquet"
# download: s3://EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/MCURExport-00001.snappy.parquet to ./MCURExport-00001.snappy.parquet
# download: s3://EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/MCURExport-00002.snappy.parquet to ./MCURExport-00002.snappy.parquet
# download: s3://EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/MCURExport-00003.snappy.parquet to ./MCURExport-00003.snappy.parquet
# download: s3://EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/MCURExport-00004.snappy.parquet to ./MCURExport-00004.snappy.parquet
# download: s3://EXAMPLE-DOC-BUCKET/mcur/MCURExport/MCURExport/year=2025/month=3/MCURExport-00005.snappy.parquet to ./MCURExport-00005.snappy.parquet

ls -1
# MCURExport-00001.snappy.parquet
# MCURExport-00002.snappy.parquet
# MCURExport-00003.snappy.parquet
# MCURExport-00004.snappy.parquet
# MCURExport-00005.snappy.parquet

DuckDBによる分析準備

以下Installationのとおりに、 DuckDBをローカルにインストールします。

https://duckdb.org/docs/installation/

DuckDBのバージョン確認、起動
duckdb --version
# v1.2.1 8e52ec4395

duckdb
# v1.2.1 8e52ec4395
# Enter ".help" for usage hints.
# Connected to a transient in-memory database.
# Use ".open FILENAME" to reopen on a persistent database.
# D

DuckDBの起動後、CUR(parquet)を読込できるかチェックします。

CUR(parquet)の読み込みテスト
SELECT * FROM '*.snappy.parquet' LIMIT 10;
-- ┌──────────────────────┬──────────────────────┬───┬──────────────────────┬──────────────────────┐
-- │ identity_line_item…  │ identity_time_inte…  │ … │ savings_plan_offer…  │ savings_plan_start…  │
-- │       varchar        │       varchar        │   │       varchar        │       varchar        │
-- ├──────────────────────┼──────────────────────┼───┼──────────────────────┼──────────────────────┤
-- │ lyrqemptwjeexample…  │ 2025-03-04T17:00:0…  │ … │ NULL                 │ NULL                 │
-- │ t5vgwtwcjnjexample…  │ 2025-03-03T04:00:0…  │ … │ NULL                 │ NULL                 │
-- │ hist3kadojfexample…  │ 2025-03-22T23:00:0…  │ … │ NULL                 │ NULL                 │
-- │ qdcaqsbach3example…  │ 2025-03-05T15:00:0…  │ … │ NULL                 │ NULL                 │
-- │ durwy3d6cz5example…  │ 2025-03-13T22:00:0…  │ … │ NULL                 │ NULL                 │
-- │ tv52j3milluexample…  │ 2025-03-12T11:00:0…  │ … │ NULL                 │ NULL                 │
-- │ yhljecfe4abexample…  │ 2025-03-11T09:00:0…  │ … │ NULL                 │ NULL                 │
-- │ d7hok52ehhlexample…  │ 2025-03-19T02:00:0…  │ … │ NULL                 │ NULL                 │
-- │ 7ep6eo4atd5example…  │ 2025-03-07T17:00:0…  │ … │ NULL                 │ NULL                 │
-- │ kl5cvl4ftyhexample…  │ 2025-03-05T02:00:0…  │ … │ NULL                 │ NULL                 │
-- ├──────────────────────┴──────────────────────┴───┴──────────────────────┴──────────────────────┤
-- │ 10 rows                                                                 283 columns (4 shown) │
-- └───────────────────────────────────────────────────────────────────────────────────────────────┘

1レコードのみ、内容も見てみます。

1レコードのみ確認
-- 出力形式を line に変更 (1レコードの内容を確認するときに便利)
.mode line

-- 1レコードのみ確認
SELECT * FROM '*.snappy.parquet' LIMIT 1;

-- option: 出力形式を元(=デフォルト)に戻す
.mode duckbox
1レコード(APN1-DataProcessing-Bytes)のサンプル
              identity_line_item_id = lvhfqje3iexample
             identity_time_interval = 2025-03-12T04:00:00Z/2025-03-12T05:00:00Z
                    bill_invoice_id = NULL
                bill_billing_entity = AWS
                     bill_bill_type = Anniversary
              bill_payer_account_id = 111111111111
     bill_billing_period_start_date = 2025-03-01 00:00:00
       bill_billing_period_end_date = 2025-04-01 00:00:00
              bill_invoicing_entity = Amazon Web Services Japan G.K.
         line_item_usage_account_id = 222222222222
           line_item_line_item_type = Usage
         line_item_usage_start_date = 2025-03-12 04:00:00
           line_item_usage_end_date = 2025-03-12 05:00:00
             line_item_product_code = AmazonCloudWatch
               line_item_usage_type = APN1-DataProcessing-Bytes
                line_item_operation = PutLogEvents
        line_item_availability_zone = NULL
              line_item_resource_id = arn:aws:logs:ap-northeast-1:222222222222:log-group:/aws/rds/cluster/example/slowquery
             line_item_usage_amount = 5.1396e-05
     line_item_normalization_factor = NULL
  line_item_normalized_usage_amount = NULL
            line_item_currency_code = USD
           line_item_unblended_rate = 0.7600000000
           line_item_unblended_cost = 3.9061e-05
    line_item_line_item_description = $0.76 per GB custom log data ingested in Standard log class - Asia Pacific (Tokyo)
                 line_item_tax_type = NULL
             line_item_legal_entity = Amazon Web Services Japan G.K.
               product_product_name = AmazonCloudWatch
                          ----- 長いので省略 -----
      pricing_lease_contract_length = NULL
             pricing_offering_class = NULL
            pricing_purchase_option = NULL
      pricing_public_on_demand_cost = 3.9061e-05
      pricing_public_on_demand_rate = 0.7600000000
                       pricing_term = OnDemand
                       pricing_unit = GB
                          ----- 長いので省略 -----
resource_tags_user_cm_billing_group = NULL
                          ----- 長いので省略 -----

コスト分析!

よく使いそうな分析をいくつか実行してみます。

日ごとの料金

日付ごとの合計コストを出力してみます。

日ごとの料金
SELECT
  CAST(line_item_usage_start_date AS DATE) AS date,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
GROUP BY date
ORDER BY date;
-- ┌────────────┬──────────┐
-- │    date    │   cost   │
-- │    date    │  double  │
-- ├────────────┼──────────┤
-- │ 2025-03-01 │     91.0 │
-- │ 2025-03-02 │     83.4 │
-- │ 2025-03-03 │     83.6 │
-- │ 2025-03-04 │     94.6 │
-- │ 2025-03-05 │     90.2 │
-- │ 2025-03-06 │    100.2 │
-- │ 2025-03-07 │     91.8 │
-- ... 省略 ...
-- │ 2025-03-26 │      0.0 │
-- │ 2025-03-27 │      0.0 │
-- │ 2025-03-28 │      0.0 │
-- │ 2025-03-29 │      0.0 │
-- │ 2025-03-30 │      0.0 │
-- │ 2025-03-31 │      0.0 │
-- ├────────────┴──────────┤
-- │ 31 rows     2 columns │
-- └───────────────────────┘
-- ※ 後半が 0.0 になっているのは3月途中でのCURを分析しているため

以下は日付をBETWEEN演算子で指定した例です。

日ごとの料金(範囲指定)
SELECT
  CAST(line_item_usage_start_date AS DATE) AS date,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
WHERE
  CAST(line_item_usage_start_date AS DATE)
    BETWEEN DATE '2025-03-01' AND DATE '2025-03-07'
GROUP BY date
ORDER BY date;
-- ┌────────────┬──────────┐
-- │    date    │   cost   │
-- │    date    │  double  │
-- ├────────────┼──────────┤
-- │ 2025-03-01 │     91.0 │
-- │ 2025-03-02 │     83.4 │
-- │ 2025-03-03 │     83.6 │
-- │ 2025-03-04 │     94.6 │
-- │ 2025-03-05 │     90.2 │
-- │ 2025-03-06 │    100.2 │
-- │ 2025-03-07 │     91.8 │
-- ├────────────┴──────────┤
-- │  7 rows     2 columns │
-- └───────────────────────┘

サービス毎の料金

AWSサービスごとの合計コストを計算して、TOP10を表示します。

AWSサービスごとのコストTOP10
SELECT
  product_product_name,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
GROUP BY product_product_name
ORDER BY cost DESC
LIMIT 10;
-- ┌────────────────────────────────────┬─────────┐
-- │        product_product_name        │  cost   │
-- │              varchar               │ double  │
-- ├────────────────────────────────────┼─────────┤
-- │ Amazon Relational Database Service │    78.6 │
-- │ Amazon Elastic Compute Cloud       │    65.4 │
-- │ AmazonCloudWatch                   │    59.8 │
-- │ Amazon CloudFront                  │    52.3 │
-- │ Amazon Simple Storage Service      │    47.9 │
-- │ AWS WAF                            │    35.6 │
-- │ Amazon Virtual Private Cloud       │    29.4 │
-- │ Amazon ElastiCache                 │    22.7 │
-- │ Amazon DynamoDB                    │    18.5 │
-- │ Amazon Elastic Container Service   │    12.8 │
-- ├────────────────────────────────────┴─────────┤
-- │ 10 rows                            2 columns │
-- └──────────────────────────────────────────────┘

特定サービス内の使用タイプ料金

特定サービス(例:S3)の使用タイプごとの合計コストを計算して、TOP10を表示します。

S3の使用タイプ別コストTOP10
SELECT
  line_item_usage_type,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
WHERE
  product_product_name = 'Amazon Simple Storage Service'
GROUP BY line_item_usage_type
ORDER BY cost DESC
LIMIT 10;
-- ┌──────────────────────────────────┬─────────┐
-- │       line_item_usage_type       │  cost   │
-- │             varchar              │ double  │
-- ├──────────────────────────────────┼─────────┤
-- │ APN1-TimedStorage-ByteHrs        │    25.3 │
-- │ APN1-Requests-Tier1              │     8.7 │
-- │ APN1-Requests-Tier2              │     5.2 │
-- │ APN1-DataTransfer-Out-Bytes      │     4.1 │
-- │ APN1-TimedStorage-GlacierByteHrs │     2.8 │
-- │ APN1-Requests-Tier3              │     1.5 │
-- │ TimedStorage-ByteHrs             │     1.0 │
-- │ APN1-EUC1-AWS-Out-Bytes          │     0.6 │
-- │ APN1-USE2-AWS-Out-Bytes          │     0.5 │
-- │ APN1-TimedStorage-GIR-ByteHrs    │     0.3 │
-- ├──────────────────────────────────┴─────────┤
-- │ 10 rows                          2 columns │
-- └────────────────────────────────────────────┘

CmBillingGroupタグ毎の料金

クラスメソッドメンバーズでは、デフォルトで 「CmBillingGroup」をコスト配分タグとして利用できます。 CmBillingGroupタグの情報は CUR の resource_tags_user_cm_billing_group にて確認できます。

以下SQLでCmBillingGroupタグごとの合計コストを計算します。

SELECT
  resource_tags_user_cm_billing_group as tag,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
GROUP BY tag
ORDER BY cost DESC
LIMIT 10;
-- ┌──────────────┬─────────┐
-- │     tag      │  cost   │
-- │   varchar    │ double  │
-- ├──────────────┼─────────┤
-- │ aaa          │   450.3 │
-- │ NULL         │   220.5 │
-- │ backend      │   105.7 │
-- │ bbb          │    75.2 │
-- │ hoge         │    58.6 │
-- │ fuga         │    35.8 │
-- │ piyo         │    22.4 │
-- │ foo          │    15.6 │
-- │ bar          │    10.2 │
-- │ test123      │     5.7 │
-- ├──────────────┴─────────┤
-- │ 10 rows      2 columns │
-- └────────────────────────┘
-- ※ NULL はタグが無いもの or 非対応のもの

リソースごとの料金

line_item_resource_id にて料金対象のリソースIDが記載されます。

例えば以下SQLでコストになっている「AWSリソースと使用タイプの組み合わせ」を計算できます。

コストになっている「AWSリソース x 使用タイプ」TOP10
SELECT
  line_item_resource_id,
  line_item_usage_type,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
GROUP BY line_item_resource_id, line_item_usage_type
ORDER BY cost DESC
LIMIT 10;
-- ┌──────────────────────────────────────────────────────────┬────────────────────────────────┬────────┐
-- │                         line_item_resource_id            │      line_item_usage_type      │  cost  │
-- │                                varchar                   │            varchar             │ double │
-- ├──────────────────────────────────────────────────────────┼────────────────────────────────┼────────┤
-- │ DOC-EXAMPLE-BUCKET1                                      │ APN1-TimedStorage-ByteHrs      │  125.8 │
-- │ DOC-EXAMPLE-BUCKET2                                      │ APN1-TimedStorage-ByteHrs      │   85.3 │
-- │ arn:aws:rds:ap-northeast-1:222222222222:cluster:aaa      │ APN1-Aurora:StorageUsage       │   68.4 │
-- │ DOC-EXAMPLE-BUCKET3                                      │ APN1-TimedStorage-ByteHrs      │   62.5 │
-- │ arn:aws:rds:ap-northeast-1:222222222222:db:bbb           │ APN1-InstanceUsage:db.r5.8xl   │   54.2 │
-- │ arn:aws:rds:ap-northeast-1:222222222222:cluster:ccc      │ APN1-Aurora:StorageUsage       │   42.6 │
-- │ DOC-EXAMPLE-BUCKET4                                      │ APN1-TimedStorage-ByteHrs      │   28.7 │
-- │ arn:aws:redshift:ap-northeast-1:222222222222:cluster:ddd │ APN1-RMS:ra3.xlplus            │   15.9 │
-- │ arn:aws:rds:ap-northeast-1:222222222222:cluster:eee      │ APN1-Aurora:StorageIOUsage     │   10.3 │
-- │ NULL                                                     │ APN1-ConfigurationItemRecorded │    6.3 │
-- ├──────────────────────────────────────────────────────────┴────────────────────────────────┴────────┤
-- │ 10 rows                                                                                  3 columns │
-- └────────────────────────────────────────────────────────────────────────────────────────────────────┘

例をもうひとつ。 コストが大きくなりがちな CloudWatch Logs の取り込み料金( DataProcessing-Bytes ) をリソースごとに表示してみます。

CloudWatch Logs 取り込み料金が多いものTOP10
SELECT
  regexp_replace(line_item_resource_id, '^arn:aws:.+:\d+:log-group:', '') as cw_log_group_name,
  round(SUM(line_item_unblended_cost),1) AS cost
FROM '*.snappy.parquet'
WHERE
  line_item_usage_type = 'APN1-DataProcessing-Bytes'
GROUP BY cw_log_group_name
ORDER BY cost DESC
LIMIT 10;
-- ┌────────────────────────────────────────┬────────┐
-- │            cw_log_group_name           │  cost  │
-- │                 varchar                │ double │
-- ├────────────────────────────────────────┼────────┤
-- │ /aws/lambda/aaa-service-get-status     │   13.0 │
-- │ app-server-logs                        │    5.6 │
-- │ /aws/lambda/store-container-function   │    3.1 │
-- │ batch-processing-stock-quantity        │    2.0 │
-- │ /aws/rds/cluster/mysql-bbb/general     │    1.3 │
-- │ ccc-custom                             │    1.1 │
-- │ ddd-logs                               │    0.7 │
-- │ batch-sync-inventory-service           │    0.6 │
-- │ api-general-logs                       │    0.5 │
-- │ api-staging-environment                │    0.4 │
-- ├────────────────────────────────────────┴────────┤
-- │ 10 rows                               2 columns │
-- └─────────────────────────────────────────────────┘

おわりに

クラスメソッドメンバーズ提供のCURを分析してみました。 とりあえずS3に出力しておくように設定して、 必要なときにDuckDBでサクッと分析する …といった使い方もアリだと思います。

以上、参考になれば幸いです。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.