クラスメソッドメンバーズ提供のCURをローカルで一つのファイルにして使ってみる

弊社サービスのクラスメソッドメンバーズにて提供しているCURをローカルで結合して一つのファイルとして扱う際の手順をまとめてみました。
2021.03.11

はじめに

弊社サービスのクラスメソッドメンバーズで提供しているCost and Usage Report(CUR)を元に、色々な計算や計測を行っているお客様も数多いかと思われます。

担当の私自身もCURを元に調査を行うことがありますが、大体はAthena上にCURを元にしたテーブルを作成し、そこからSQLを実行して計算することがほとんどです。

ただ、単純に該当する条件の行を只管出力したい時もあります。あるいはファイルフォーマットの調査時は必然とCURを直接開く必要がでてきます。問題は、該当するCURのファイル数です。処理の都合上400個近いファイルに分割されていることも。

需要があるかどうかは分かりませんが、調査の手間を減らすために数百個に分割されたCURを手元にて一つにする方法を書いてみました。

メンバーズ提供のCUR構成

日付毎に出力されたCURファイル名の一覧は同じく出力されるcur_hourly-Manifest.jsonに記載されており、この中のファイルを一通り開くことで明細レコードをもれなく取得することが可能です。管理コンソールでは一番最後に表示されるため、最後のページの最後の行までたどる必要があります。

このcur_hourly-Manifest.jsonを元に、ファイルを一つに結合してみます。圧縮しての結合か、そのままの結合にするかによって若干手続きが異なります。

圧縮出力する

以下の順に処理を行います。

  1. 指定バケットを再帰一覧化する
  2. cur_hourly-Manifest.jsonファイルのみに絞る
  3. 指定日付のcur_hourly-Manifest.jsonに絞る
  4. バケットからコピーして標準入力へ流す
  5. jqにてファイル一覧を取得する
  6. バケットからファイル一覧を元に明細を標準入力へ流す
  7. 解凍し行指定で取得して再度圧縮した後ファイルへ出力する

なお、CURのファイル夫々にヘッダが含まれているため、まずはヘッダを一行取得した後、各CURからヘッダを除いて出力します。

ヘッダを出力する

以下のコマンドを実行します。

% export BUCKET_NAME=XXXX
% export TARGET_DATE=<TARGET_DATE> # like 2021-01-01
% aws s3 ls --recursive s3://${BUCKET_NAME}/ |
    grep cur_hourly-Manifest.json | 
    awk -v b="$BUCKET_NAME" -v d="$TARGET_DATE" '{ if($1 == d) { print "s3://" b "/" $4 }}' |
    xargs -I{} aws s3 cp {} - |
    jq -r ".reportKeys[0]" | 
    xargs -n1 -I{} aws s3 cp s3://${BUCKET_NAME}/{} - | 
    gunzip |
    sed -ne "1p" |
    gzip |
    tee - > source.tar.gz

gzcatでsource.tar.gzに出力されているか確認します。

% gzcat source.tar.gz
identity/LineItemId,identity/TimeInterval,bill/BillingEntity,bill/BillType...

続いて明細レコードを取り込みます。

各ファイルからヘッダを除いて明細を取得する

実行後暫く待ちます。

% aws s3 ls --recursive s3://${BUCKET_NAME}/ |
    grep cur_hourly-Manifest.json | 
    awk -v b="$BUCKET_NAME" -v d="$TARGET_DATE" '{ if($1 == d) { print "s3://" b "/" $4 }}' | 
    xargs -I{} aws s3 cp {} - | 
    jq -r ".reportKeys[]" | 
    xargs -n1 -I{} aws s3 cp s3://${BUCKET_NAME}/{} - | 
    gunzip | 
    sed -e '1d' | 
    gzip | 
    tee - >> source.tar.gz

バケット内にオブジェクトが多数溜まっている場合には時間が掛かります。再帰する対象を絞るか、別途バケットを作成した上で対象日付のディレクトリのコピーをおすすめします。

出力されているかどうかをヘッダ確認時と同じようにgzcatで確認します。

% gzcat source.tar.gz | sed -n 1,4p
identity/LineItemId,identity/TimeInterval,bill/BillingEntity,bill/BillType...
ooooooooooooooooooooooooooooooooooooooooooooooooooooo,2021-02-07T00:00:00Z/2021-02-07T01:00:00Z...
..

未圧縮出力する

以下の順に処理を行います。

  1. 指定バケットを再帰一覧化する
  2. cur_hourly-Manifest.jsonファイルのみに絞る
  3. 指定日付のcur_hourly-Manifest.jsonに絞る
  4. バケットからコピーして標準入力へ流す
  5. jqにてファイル一覧を取得する
  6. バケットからファイル一覧を元に明細を標準入力へ流す
  7. 解凍し行指定で取得してファイルへ出力する

圧縮時と比べてファイルサイズが大きくなります。

ヘッダを出力する

以下のコマンドを実行します。

% export BUCKET_NAME=XXXX
% export TARGET_DATE=<TARGET_DATE> # like 2021-01-01
% aws s3 ls --recursive s3://${BUCKET_NAME}/ |
    grep cur_hourly-Manifest.json | 
    awk -v b="$BUCKET_NAME" -v d="$TARGET_DATE" '{ if($1 == d) { print "s3://" b "/" $4 }}' |
    xargs -I{} aws s3 cp {} - |
    jq -r ".reportKeys[0]" | 
    xargs -n1 -I{} aws s3 cp s3://${BUCKET_NAME}/{} - | 
    gunzip |
    sed -ne "1p" |
    tee - > source.csv

catでsource.csvに出力されているか確認します。

% cat source.csv
identity/LineItemId,identity/TimeInterval,bill/BillingEntity,bill/BillType...

続いて明細レコードを取り込みます。

各ファイルからヘッダを除いて明細を取得する

実行後暫く待ちます。

% aws s3 ls --recursive s3://${BUCKET_NAME}/ |
    grep cur_hourly-Manifest.json | 
    awk -v b="$BUCKET_NAME" -v d="$TARGET_DATE" '{ if($1 == d) { print "s3://" b "/" $4 }}' | 
    xargs -I{} aws s3 cp {} - | 
    jq -r ".reportKeys[]" | 
    xargs -n1 -I{} aws s3 cp s3://${BUCKET_NAME}/{} - | 
    gunzip | 
    sed -e '1d' | 
    tee - >> source.csv

バケット内にオブジェクトが多数溜まっている場合には時間が掛かります。再帰する対象を絞るか、別途バケットを作成した上で対象日付のディレクトリのコピーをおすすめします。

出力されているかどうかをヘッダ確認時と同じようにcatで確認します。

% cat source.csv | sed -n 1,4p
identity/LineItemId,identity/TimeInterval,bill/BillingEntity,bill/BillType...
ooooooooooooooooooooooooooooooooooooooooooooooooooooo,2021-02-07T00:00:00Z/2021-02-07T01:00:00Z...
..

未圧縮の場合にはcsvqも利用できます。

% csvq 'select count(*) from `source.csv`'
+----------+
| COUNT(*) |
+----------+
|   185027 |
+----------+

あとがき

CURのバケットにある大量のバケットを見て、どう扱うべきか戸惑った場合には今回の手順を試してみると良いかもしれません。Amazon Athenaを使いたい場合には次の記事を参考にしてみてください。