S3バケットのオブジェクト数やサイズを確認する前に知っておきたいこと(LISTリクエストとS3インベントリ)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。DA事業本部の春田です。

年末の大掃除のように、たまに行いたいS3オブジェクトの棚卸し作業。AWS CLIを使って何も考えずにバケットの中身をチェックしてると、請求金額が結構お高くつきますよ、という注意書きです。

S3のLISTリクエスト

バケットの中身を確認する時、AWS CLIのaws s3 lsというコマンドがよく使われますが、これの裏で動いているLISTリクエストの料金は、1,000リクエスト毎に0.0047USDかかります。1リクエストで取得できるオブジェクトの最大数は1,000件なので、0.0047USDで最大100万個のオブジェクトの情報を取得することができます。

1,000obj × 1,000req = 1,000,000obj / 0.0047USD

気をつけなければいけないのは、S3に大量のオブジェクト、例えば1億個以上のオブジェクトに対して日次でLISTリクエストをかけている場合、月14.1USDも支払っていることになります。

100,000,000obj ÷ 1,000obj ÷ 1,000req × 0.0047USD × 30days = 14.1USD

大量のオブジェクトをLISTで取得するのは効率が悪いですし、何しろお金がもったいないです。データの棚卸しをしたい時は、S3インベントリがおすすめです。

S3インベントリ

S3インベントリは、バケット内のオブジェクト情報、メタデータなどを一挙に出力する機能です。CSVやORC、Parquetといった出力形式に対応しているので、Athenaからそのまま参照できます。なにより、LISTリクエストと比べて料金が安いです。こちらは100万個あたり0.0028USDなので、LISTリクエストのおおよそ半額ですね。

インベントリデータの出力先は別アカウントのバケットにも指定できるため、バケット間でオブジェクト情報を渡す時にも便利です。Glueのクローラを組み合わせれば、連携したいS3バケットのデータを自動でRedshiftにロードすることもできます。

AWS Glue および Amazon Redshift を使用して Amazon S3 の利用料金を分析する | Amazon Web Services ブログから拝借)

ただし、日次または週次のスケジュール実行になるので、アドホックにインベントリデータを取得できません。オブジェクトの数にもよりますが、S3インベントリを設定して翌日ぐらいにならないとインベントリデータが出力されないのが難点ですね。そのため、S3の棚卸し作業を行う時は、事前に手順計画を立ててから行うことをおすすめします。

実際に使ってみた

S3インベントリの設定

今回のサンプルデータは、Registry of Open Data on AWSにてオープンデータとして提供されているCOVID-19 Data LakeのS3バケットのデータを使用します。2020年5月26日17時現在のオブジェクト数は約3万個でした。ちゃっかりaws s3 lsを使ってますが悪しからず。

$ aws s3 ls s3://covid19-lake --recursive --human-readable --summarize
2020-03-25 04:56:58    0 Bytes alleninstitute/CORD19/comprehendmedical/
2020-03-31 10:23:34   13.5 MiB alleninstitute/CORD19/comprehendmedical/comprehend_medical.json
2020-05-26 17:33:05   76.7 MiB alleninstitute/CORD19/json/metadata/part-00000-0cb5c32b-66b3-4002-b923-6f13be3723eb-c000.json
2020-03-21 02:13:06    0 Bytes alleninstitute/CORD19/raw/
2020-03-29 01:25:22   71.3 KiB alleninstitute/CORD19/raw/biorxiv_medrxiv/0015023cc06b5362d332b3baf348d11567ca2fbb.json
...
2020-05-26 17:34:46  150.4 MiB tableau-covid-datahub/json/part-00000-95caaef8-a5c5-4de9-abac-fef109d7802d-c000.json
2020-05-26 12:39:54  179.5 MiB tableau-jhu/csv/COVID-19-Cases.csv
2020-05-21 20:38:58   80.0 MiB tableau-jhu/json/part-00000-0755f7e6-871b-4297-a4c3-c1f94ed3ebf1-c000.json
2020-05-21 20:39:01   80.1 MiB tableau-jhu/json/part-00001-0755f7e6-871b-4297-a4c3-c1f94ed3ebf1-c000.json
2020-05-21 20:38:57   55.5 MiB tableau-jhu/json/part-00002-0755f7e6-871b-4297-a4c3-c1f94ed3ebf1-c000.json

Total Objects: 33840
   Total Size: 37.8 GiB

インベントリの機能を使いたいので、自分の環境のバケットにsyncします。サイズ的に3時間くらい時間がかかりそうなので、EC2インスタンス上でnohupで実行しました。

$ aws s3 sync s3://covid19-lake s3://cm-haruta/covid19-lake/

対象のS3バケットに対してインベントリの設定を行います。まず、棚卸しをしたいソースバケットに入り、管理のタブを押下します。

インベントリを押下します。

他にインベントリがない場合は、以下の画面が表示されます。新規追加をクリックします。

インベントリ情報を入力していきます。フィルターはインベントリの対象としたいprefixを入力します。今回はcovid19-lake配下のデータを対象としますが、バケット全体にしたい場合は空欄のままで大丈夫です。連携先バケットは、リージョンが同じであれば別アカウントのバケットも指定することができます。今回は同じバケットcm-harutainventory配下に出力させます。頻度は日別(日次)にします。

インベントリでは、全バージョンも含められたり、どのメタデータを抽出するかも事前に選択することができます。KMSと連携して暗号化も可能です。

インベントリが正常に作成されると、出力先に反映させるバケットポリシーのJSONを自動で表示してくれます。ソースバケットと出力先のバケットが異なる場合、書き込み用のバケットポリシーの設定もお忘れなく。

バケットポリシーはアクセス権限のタブから設定できます。既存のポリシーがなければ、先ほどのJSONをそのままコピペしてください。

これで準備完了です。インベントリ出力には時間がかかるので、一晩か二晩寝かせておきましょう。

AthenaからS3インベントリを参照する

2日後、期待したインベントリデータが作成されてました。

前節で設定したinventory/配下に、バケット名と対象のprefixが続き、以下のようなディレクトリ構造でデータが作成されます。

inventory/cm-haruta/covid19-lake/
├ 2020-05-25T00-00Z/
│ ├ manifest.checksum
│ └ manifest.json                                -> 各種AWSサービスで使用できるインベントリデータのKeyやメタデータ
├ 2020-05-26T00-00Z/
│ └
├ 2020-05-27T00-00Z/
│ └
├ data/
│ ├ 2e99f2bd-0b34-4237-9c84-b208e2f8b1f6.parquet -> 25日分のインベントリデータ
│ └ 9a53bd0c-9f1c-478b-90da-f5584a846d97.parquet -> 26日分のインベントリデータ
└ hive/
  ├ dt=2020-05-25-00-00/
  │  └ symlink.txt                               -> Hive用の25日分のインベントリデータのKey
  ├ dt=2020-05-26-00-00/
  │  └
  └ dt=2020-05-27-00-00/
     └

data/配下に対して、AWS Glueのクローラをかけてスキーマ情報を取得します。最終的なクローラの設定は以下のような感じです。

クローラを実行して、作成されたテーブルは以下の通りです。データベース出力で設定したcm-harutaに対して、Athenaからクエリをかけられるようになりました。

Athenaからプレビューして中身を見てみます。

SELECT * FROM "cm-haruta"."data";
カラム名 データ例
bucket cm-haruta
key covid19-lake/alleninstitute/CORD19/comprehendmedical/comprehend_medical.json
version_id
is_latest TRUE
is_delete_marker FALSE
size 14136397
last_modified_date 2020-05-26 07:27:51.000

インベントリを設定した時に選択したカラムは「サイズ」と「最終更新日」でしたので、それ以外はデフォルトで抽出される情報のようですね。過不足ないメタデータで使いやすそうです。

データの棚卸しができた完了したところで、prefixごとにサイズや最終更新日を以下のSQLで集計してみます。

WITH tidy AS (
  SELECT
    CASE
      WHEN regexp_like(s3.key, '^covid19-lake/alleninstitute/') THEN 'alleninstitute'
      WHEN regexp_like(s3.key, '^covid19-lake/archived/') THEN 'archived'
      WHEN regexp_like(s3.key, '^covid19-lake/cfn/') THEN 'cfn'
      WHEN regexp_like(s3.key, '^covid19-lake/covid_knowledge_graph/') THEN 'covid_knowledge_graph'
      WHEN regexp_like(s3.key, '^covid19-lake/covidcast/') THEN 'covidcast'
      WHEN regexp_like(s3.key, '^covid19-lake/enigma-aggregation/') THEN 'enigma-aggregation'
      WHEN regexp_like(s3.key, '^covid19-lake/enigma-jhu-timeseries/') THEN 'enigma-jhu-timeseries'
      WHEN regexp_like(s3.key, '^covid19-lake/enigma-jhu/') THEN 'enigma-jhu'
      WHEN regexp_like(s3.key, '^covid19-lake/enigma-nytimes-data-in-usa/') THEN 'enigma-nytimes-data-in-usa'
      WHEN regexp_like(s3.key, '^covid19-lake/rearc-covid-19-nyt-data-in-usa/') THEN 'rearc-covid-19-nyt-data-in-usa'
      WHEN regexp_like(s3.key, '^covid19-lake/rearc-covid-19-prediction-models/') THEN 'rearc-covid-19-prediction-models'
      WHEN regexp_like(s3.key, '^covid19-lake/rearc-covid-19-testing-data/') THEN 'rearc-covid-19-testing-data'
      WHEN regexp_like(s3.key, '^covid19-lake/rearc-covid-19-world-cases-deaths-testing/') THEN 'rearc-covid-19-world-cases-deaths-testing'
      WHEN regexp_like(s3.key, '^covid19-lake/rearc-usa-hospital-beds/') THEN 'rearc-usa-hospital-beds'
      WHEN regexp_like(s3.key, '^covid19-lake/safegraph-open-census-data/') THEN 'safegraph-open-census-data'
      WHEN regexp_like(s3.key, '^covid19-lake/static-datasets/') THEN 'static-datasets'
      WHEN regexp_like(s3.key, '^covid19-lake/tableau-covid-datahub/') THEN 'tableau-covid-datahub'
      WHEN regexp_like(s3.key, '^covid19-lake/tableau-jhu/') THEN 'tableau-jhu'
    ELSE 'others' END AS key,
    s3.size,
    date_format(s3.last_modified_date, '%Y-%m-%d') AS last_modified_date
  FROM "cm-haruta"."data" AS s3
)
SELECT
  tidy.key,
  ROUND(CAST(SUM(tidy.size) AS DOUBLE) / 1024 / 1024 / 1024, 2) AS size_gb,
  MIN(tidy.last_modified_date) AS min_date,
  MAX(tidy.last_modified_date) AS max_date
FROM tidy
GROUP BY key
ORDER BY key
key size_gb min_date max_date
alleninstitute 4.24 2020-05-26 2020-05-26
archived 0.54 2020-05-26 2020-05-26
cfn 0.0 2020-05-26 2020-05-26
covid_knowledge_graph 1.14 2020-05-26 2020-05-26
covidcast 4.12 2020-05-26 2020-05-26
enigma-aggregation 0.03 2020-05-26 2020-05-26
enigma-jhu 0.05 2020-05-26 2020-05-26
enigma-jhu-timeseries 0.03 2020-05-26 2020-05-26
enigma-nytimes-data-in-usa 0.02 2020-05-26 2020-05-26
others 0.0 2020-05-26 2020-05-26
rearc-covid-19-nyt-data-in-usa 0.02 2020-05-26 2020-05-26
rearc-covid-19-prediction-models 0.01 2020-05-26 2020-05-26
rearc-covid-19-testing-data 0.0 2020-05-26 2020-05-26
rearc-covid-19-world-cases-deaths-testing 0.01 2020-05-26 2020-05-26
rearc-usa-hospital-beds 0.0 2020-05-26 2020-05-26
safegraph-open-census-data 27.01 2020-05-26 2020-05-26
static-datasets 0.0 2020-05-26 2020-05-26
tableau-covid-datahub 0.18 2020-05-26 2020-05-26
tableau-jhu 0.39 2020-05-26 2020-05-26

第一階層ごとのオブジェクトサイズの合計と、最終変更日の最小値・最大値が取得できました。safegraph-open-census-data/が容量食ってるようなので、ここにS3ライフサイクルポリシーを設定してあげると良さそうですね。

まとめ

aws s3 lsコマンドを頻繁に使っている方、大量のオブジェクトに対してLISTリクエストをかけたい方は、S3インベントリの使用を検討してみてください。コスト効率がよく、GlueとAthenaを使えば簡単に集計用のSQLを叩くことができます。

参照