S3 Batch Operationsを使用してオブジェクトをコピーした後、Athenaとインベントリレポートを使用してコピーできているか確認してみた

こんにちは。枡川です。

S3 Batch Operationsを使用してオブジェクトをコピーした後、Athenaを使用してインベントリレポートをクエリして確認するまでを一通りやってみました。
Batch Operationsについては下記ブログで紹介されてます。

Athenaを使用したインベントリレポートへのクエリの下記ブログで紹介されています。

マネジメントコンソールのUIが大分変わっていたことと、コピーした後インベントリレポートで確認するまでを一通り確認したかったため、今回記事に起こしました。

シナリオ

東京リージョンのS3から大阪リージョンのS3へバケット内のオブジェクトをコピーすることを想定します。
クロスリージョンレプリケーションを設定することで自動でオブジェクトがコピーされますが、既存のオブジェクトはコピーされないため、それらをコピーするためにBatch Operationsを使用することとします。
この場合、下記ブログのようにサポートに依頼する方法も取れるのですが、何日で完了するか定められていないため、すぐにコピーしたい場合はAWS CLIやBatch Operationsを使用することになります。

Batch Operationsを使用してコピーを実施した後、Athenaでインベントリレポートをクエリしてオブジェクトが正しくコピーされていることを確認します。
インベントリレポートはS3に出力する必要があるので、新しくバケットを作成してそこに出力することにします。
出力先には他リージョンのバケットを選択することができないため、今回東京と大阪にインベントリレポート出力用のバケットを作成します。

インベントリレポートの発行

インベントリレポートはS3バケット内のオブジェクトのリストと、メタデータを一覧にしたレポートとなります。
S3の機能として出力することが可能であり、このレポートとAthenaを組み合わせることでバケット内のオブジェクト情報を簡単にクエリすることができます。
また、S3に対するバッチ操作を行うBatch Operationsもこのレポートを使用して実施します。
Batch Operationsはインベントリレポート以外を使用した場合も実行可能ですが、インベントリレポートを使用するのが楽なのでこれを使用することを最優先に考えて良いと思います。

まず、S3バケットの管理 > インベントリ設定に移動して、インベントリ設定の作成をクリックします。
次にインベントリ設定名とインベントリスコープを設定します。
バケット内の一部のオブジェクトに絞って出力することができますが、今回はバケット内の全オブジェクトを対象としたいため特に設定しません。

次にレポートの送信先を設定します。
この際のprefixについてですが、設定はしなくてもレポート名/バケット名という形で自動でフォルダを作ってくれます。
そのため、インベントリレポート専用のバケットを作成していれば不要に思えました。
様々な種類のデータを一つのバケットに集めている場合は、inventory-report等でプレフィクスを設定しても良いかもしれません。
また、コンソールにちゃんと書いてありますが、形式はs3:://bucket/prefixです。
最後に/を付けて指定するとprefix//レポート名/バケット名として生成されてしまいます。
Athenaでインベントリレポートをクエリする際にダブルスラッシュがあると上手くファイルを指定することができないためクエリを実行できなくなります。
[参考]Amazon Athena テーブルに対してクエリを実行するときにゼロ個のレコードが返されるのはなぜですか?
インベントリレポートも問題なく出力でき、AthenaのCREATE TABLE時にも明確なエラーは出ないがクエリの結果が0列になってしまいます。
私はこれが原因でやり直しました...(ちゃんとコンソールに書いてあるのでハマる人はあまりいないと思います) 送信先のバケットポリシーについては設定時に自動で許可を追加してくれます。
頻度は日別、出力形式はCSVとします。
AWS公式ドキュメントには下記記載がありますが、Batch Operationsを使用するのであればCSVである必要があるのと、CSV形式でも問題無くクエリできます(パフォーマンスには多少影響があると思います)。

Athena を使用してインベントリをクエリする場合は、ORC 形式または Parquet 形式のインベントリファイルを使用することをお勧めします。
[参考]Amazon Athena で Amazon S3 インベントリをクエリする

最初のレポートは48時間以内に配信されますと記載がありますが、私が3回設定した中では毎回次の日には出力されていました。
2回目以降のレポートは明朝に前日分が出力されていました。
この辺りは制御することができない部分のため、あくまで1日1回配信と最初は48時間以内ということしか明確には言えません。
また、1日のどの時点のレポートかも明確な記載を見つけることができませんでした。
クロスリージョンレプリケーション設定を行った後に既存のオブジェクトをコピーしたい場合や、暗号化設定を有効化した後に既存のオブジェクトも暗号化したい場合は設定変更の翌日分以降のレポートを使用する必要があります。
翌日朝に前日分のレポートが出力されていることを考えると、Batch Operationsジョブを実行するのは設定変更の2日後以降が望ましいということになります。
今回はオプションとして、サイズ、最終更新日時、マルチパートアップロード、レプリケーションステータス、Etagを有効化してみます。

Batch Operationsによるコピー

S3 Batch Operationsを使用したコピーを実施していきます。
バッチオペレーションとマネジメントコンソールの検索窓に入れれば直接移動できますし、S3のページからも移動できます。
今回は東京(ap-northeast-1)のバケットから大阪(ap-northeast-3)のバケットへのコピーを実施するため、大阪側にBatch Operationsのジョブを作成します。
コピーの場合は、送信先のバケットが存在するリージョンにジョブを作成することになります。

Batch Operationsでは、マニフェストと呼ばれる操作の対象を一覧化したリストが必要になります。
CSV形式のインベントリレポートを出力している場合、それを使用することができます。
オペレーションはコピーするを選択します。
コピー先はbatch-operation-destination-bucketとして大阪に作成しているバケットを選択します。
プレフィクス、ストレージクラス、サーバー側の暗号化、オブジェクトタグ、メタデータ、ACLは要件があれば選択しますが、今回はデフォルト設定で進めます。
優先順位についても複数のジョブを立てて優先度を制御しない限り不要です。
完了レポートはジョブの実行結果をS3に出力してくれる機能となります。
どのオブジェクトのコピーが成功して、どのオブジェクトのコピーが失敗したかをまとめてくれるため、生成しておくと良いと思います。
IAMロールについては送信元バケットや送信先バケットを入力するとそれに応じたポリシーを表示してくれます。
複数のジョブを作成する場合は、下記のようなCloudFormationテンプレートで複数を同時に作成してしまうと楽だと思います。

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SystemName:
    Description: A system name that is prefixed to resource names
    Type: String
  SourceBucket1:
    Description: A source bucket name
    Type: String
  DestinationBucket1:
    Description: A destination bucket name
    Type: String
  InventoryBucket1:
    Description: An inventory bucket name
    Type: String
  TaskReportBucket1:
    Description: A task report bucket name
    Type: String
Resources:
  BucketBackupRole1:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub ${SystemName}-${SourceBucket1}-batch-operation-role
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service:
                - batchoperations.s3.amazonaws.com
      Policies:
        - PolicyName: BatchOperaionPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectAcl
                  - s3:GetObjectTagging
                Resource:
                  - !Sub "arn:aws:s3:::${SourceBucket1}/*"
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${InventoryBucket1}/*"
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:PutObjectAcl
                  - s3:PutObjectTagging
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:GetBucketObjectLockConfiguration
                Resource:
                  - !Sub "arn:aws:s3:::${DestinationBucket1}/*"
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${TaskReportBucket1}/*"

ジョブを作成すると実行のための確認待ちという状態になります。
ユーザー側の確認が必要という意味になりますので、確認してジョブの実行をクリックします。
ジョブ実行後は成功した割合と失敗した割合を確認可能です。
コピー自体の成否はこの時点で確認できます。
必要なオブジェクトがすべてコピーされていることを確認するために、Athenaを使用して送信元バケットのインベントリレポートと送信先のインベントリレポートを比較してみます。

Athenaを使用したインベントリレポートのクエリ

Batch Operationsでコピーした後もその日のインベントリレポートに反映されているかは不明なので、次の日分のレポートを使用するのが良いかと思います。
今回は明らかに反映されていたのでコピー当日分のインベントリレポートを使用して確認しました。
まず送信元のインベントリレポートからテーブルを作成します。
カラム名とLOCATIONで指定する参照先は環境によって変更が必要です。

CREATE EXTERNAL TABLE IF NOT EXISTS source(
    bucket string, 
    key string, 
    size bigint, 
    last_modified_date string, 
    e_tag string, 
    is_multipart_uploaded boolean, 
    replication_status string
)
PARTITIONED BY (
    dt string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat'
LOCATION 's3://batch-operation-source-inventory-report-bucket/batch-operation-source-bucket/batch-operation-test-source-report/hive/';

テーブルにパーティションの情報を登録するために、下記を実行します。
この操作はインベントリデータが追加された時に都度実施する必要があります。

MSCK REPAIR TABLE source;

正しく動作すれば下記のようなログが出力されます。

Partitions not in metastore:    source:dt=2022-01-16-01-00
Repair: Added partition to metastore source:dt=2022-01-16-01-00

実行後は下記で現在クエリできるインベントリレポートの日付を表示できます。

SELECT DISTINCT dt FROM "source" ORDER BY 1 DESC;

全く同じ操作を送信先バケットのインベントリレポートに対して実行します。

CREATE EXTERNAL TABLE IF NOT EXISTS destination(
         bucket string,
         key string,
         size bigint,
         last_modified_date string,
         e_tag string,
         is_multipart_uploaded boolean,
         replication_status string
)
PARTITIONED BY (
        dt string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat'
LOCATION 's3://batch-operation-destination-inventory-report-bucket/batch-operation-destination-bucket/batch-operation-test-destination-report/hive/';

パーティション情報を反映させます。

MSCK REPAIR TABLE destination;

送信元バケットに存在して送信先バケットに存在しないオブジェクトをクエリし、1レコードも返ってこないことからすべてのオブジェクトがコピーされていることを確認できました。

SELECT
  key, size
FROM
  "source"
WHERE dt = '2022-01-17-01-00'
AND
  NOT EXISTS (
    SELECT
      key, size
    FROM
      "destination"
    WHERE
      "destination".dt = '2022-01-17-00-00' AND "source".key = "destination".key AND "source".size = "destination".size
  )
;

ある時点までのオブジェクトがコピーできていることを確認するのとは別に、適当なタイミングで下記クエリでを実行してレプリケーションが上手く行われているかも確認できます。

SELECT * FROM "source"
WHERE dt='2022-xx-xx-xx-xx'
AND last_modified_date > '2022-xx-xx-xx-xx'
AND replication_status != 'COMPLETED'
;

また、マルチパートアップロードでアップロードされていない場合はEtagでコピー結果を検証することも可能でした。

SELECT
  key, size, e_tag
FROM
  "source"
WHERE dt = '2022-01-17-01-00'
AND
  NOT EXISTS (
    SELECT
      e_tag
    FROM
      "destination"
    WHERE
      "destination".dt = '2022-01-17-00-00' AND "source".e_tag = "destination".e_tag
  )
;

ファイルの整合性チェックまで行われるのでより安心と言えそうですが、マルチパートアップロードがあった時のケアが面倒に思えます。
手元のmd5値が2f282b84e7e608d5852449ed940bfc51のファイルをアップロードした時にEtagが64348d5fe7b4b5dc40945dc62b3a4f09-7と異なるだけでなく、それを別リージョンのバケットにコピーした時にも0c4af570340e15f2be9924e70bc34257-13とさらに別の値になっています。
頑張れば計算もできるみたいですが、今回はファイル名とサイズでコピーできたことを確認できたとします。

まとめ

S3のオブジェクトをコピーする際はs3 syncしか使用したことが無かったですが、コピー後の確認まで考えるとBatch Operationsもとても便利でした。
特にそれなりに量がある場合は、マネージドサービスでコピーできるということもあり安心感があると思います。
今回の場合だと複数選択肢はありますが、選択肢の1つとして是非検討してみて下さい。