KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

S3 イベント通知とオブジェクトのストレージクラス変更で発生する CopyObject イベントについて

処方箋データ基盤チームでエンジニアをしている岩佐 (孝浩) です。 カケハシには「岩佐」さんが複数名在籍しており、社内では「わささん」と呼ばれています。

私が所属する処方箋データ基盤チームは、日本全国の薬局から送信される処方箋データを S3 に保存しています。 今後ストレージコストの増加が予測されるため、S3 ライフサイクル・ルールを利用してストレージクラスを変更することにしました。 この S3 バケットには EventBridge 通知が設定されており、ふと、「ストレージクラスの変更でイベントが発生するのでは」と気になりました。

結論として、ストレージクラスを変更すると、CopyObject イベントが発生します。 S3 バケットにイベント通知を設定している場合、このイベントを意識していないと、予期せぬ結果を引き起こす可能性があり、注意が必要です。

この投稿では、S3 バケットに EventBridge 通知が設定されている場合の動作検証と対応方法について、備忘録も兼ねて紹介します。

AWS リソース作成

まずは、検証用の AWS リソースを作成します。

S3 バケット作成

S3 バケットを作成し、EventBridge 通知を有効化します。

# S3 バケット名
bucket=storage-class-event-test-$(uuidgen | tr -d - | tr '[:upper:]' '[:lower:]')

# S3 バケット作成
aws s3api create-bucket \
  --bucket $bucket \
  --region ap-northeast-1 \
  --create-bucket-configuration LocationConstraint=ap-northeast-1

# EventBridge 通知有効化
aws s3api put-bucket-notification-configuration \
  --bucket $bucket \
  --notification-configuration='{ "EventBridgeConfiguration": {} }'

検証用オブジェクトのアップロード

検証用オブジェクトとして、任意のオブジェクトをアップロードします。 この投稿では、カケハシのテックブログのページをアップロードしています。

# Download
curl -o example.html https://kakehashi-dev.hatenablog.com/entry/2024/11/14/100000

# サイズ確認
ls -lah example.html
# -rw-r--r--. 1 cloudshell-user cloudshell-user 133K Nov 24 08:06 example.html

# Upload
aws s3 cp example.html s3://$bucket/example.html

ライフサイクル・ルールを利用してストレージクラスを変更する場合、128KB 未満のオブジェクトはストレージクラスが変更されませんので、ご注意ください。

Objects smaller than 128 KB will not transition by default to any storage class

EventBridge Rule 作成

EventBridge Rule を作成し、ターゲットとして CloudWatch Logs を設定します。

# Event Pattern
pattern=$(echo '{
  "source": ["aws.s3"],
  "detail-type": ["Object Created"],
  "detail": {
    "bucket": {
      "name": ["<BUCKET>"]
    }
  }
}' | sed -e "s/<BUCKET>/$bucket/" | jq -r .)

# Event Rule 作成
rule_name=storage-class-event-test-rule
aws events put-rule \
  --name $rule_name \
  --event-pattern "$pattern"

# CloudWatch Logs 作成
log_group_name=/aws/events/$rule_name
aws logs create-log-group \
  --log-group-name $log_group_name
  
# CloudWatch Logs ARN 取得
log_group_arn=$(
  aws logs describe-log-groups \
    --log-group-name-prefix $log_group_name \
  | jq -r '.logGroups[0].arn'
)

# Event Target 作成
aws events put-targets \
  --rule $rule_name \
  --targets "Id"="cw-logs","Arn"="$log_group_arn"

ここで設定している Event Pattern が注意すべきポイントで、後ほど動作検証します。

以下のキャプチャのとおり、Management Console で作成した場合、この Event Pattern を設定しているケースが多いと推測されるので、必要に応じて設定の見直しをお勧めします。

EventBridge Rule Event Pattern

動作検証1回目

ストレージクラス変更

この投稿では、AWS CLI で既存オブジェクトのストレージクラスを変更します。 その他の方法については、公式ドキュメントをご参照ください。

# Standard IA に変更
aws s3 cp s3://$bucket/example.html s3://$bucket/example.html \
  --storage-class STANDARD_IA

CloudWatch Logs 確認

EventBridge Rule のターゲットに設定した CloudWatch Logs を確認します。

log_stream_name=$(
  aws logs describe-log-streams \
    --log-group-name $log_group_name \
    --order-by LastEventTime \
    --descending \
  | jq -r '.logStreams[0].logStreamName'
)

aws logs get-log-events \
  --log-group-name $log_group_name \
  --log-stream-name $log_stream_name \
| jq '.events[].message | fromjson'

以下のようなイベントが記録されているはずです。

{
  "version": "0",
  "id": "12bcd22e-81e4-bba5-2037-95ab37991eaa",
  "detail-type": "Object Created",
  "source": "aws.s3",
  "account": "123456789012",
  "time": "2024-11-24T08:23:39Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:s3:::storage-class-event-test-<UUID>"
  ],
  "detail": {
    "version": "0",
    "bucket": {
      "name": "storage-class-event-test-<UUID>"
    },
    "object": {
      "key": "example.html",
      "size": 136162,
      "etag": "2fb8b1f49c6b495c614f1f7a7efb8835",
      "sequencer": "006742E28B93AB9A39"
    },
    "request-id": "8TP3RRRX0ZYF0HJP",
    "requester": "123456789012",
    "source-ip-address": "xxx.xxx.xxx.xxx",
    "reason": "CopyObject"
  }
}

動作検証結果

上で確認したとおり、現在設定されている Event Pattern では、ストレージクラスの変更時に発生する CopyObject イベントも処理してしまいます。

次の条件に該当する場合、Event Pattern の見直しが必要です。

  • ストレージクラスの変更時に発生するイベントを処理した場合、データの不整合が生じる。(冪等性を考慮した設計になっていない)
  • 膨大な数のオブジェクトが、ストレージクラスの変更対象になる。
  • Object Created - CopyObject イベントを処理する必要が無い。

Event Pattern 再設定による対応

ストレージクラスの変更時に発生する Object Created - CopyObject イベントを処理しないように、Event Pattern に reason を追加します。

@@ -10,6 +10,9 @@
       "name": [
         "storage-class-event-test-<UUID>"
       ]
-    }
+    },
+    "reason": [
+      "PutObject"
+    ]
   }
 }
# Event Pattern
pattern=$(echo '{
  "source": ["aws.s3"],
  "detail-type": ["Object Created"],
  "detail": {
    "bucket": {
      "name": ["<BUCKET>"]
    },
    "reason": [
      "PutObject"
    ]
  }
}' | sed -e "s/<BUCKET>/$bucket/" | jq -r .)

aws events put-rule \
  --name $rule_name \
  --event-pattern "$pattern"

動作検証2回目

CloudWatch Logs 再作成

ストレージクラスの変更が EventBridge Rule で処理されていないことを確認するため、事前に CloudWatch Logs を再作成しておきます。

# 削除
aws logs delete-log-group \
  --log-group-name $log_group_name

# 作成
aws logs create-log-group \
  --log-group-name $log_group_name

ストレージクラス変更

Standard IA に変更したオブジェクトを Standard に戻します。

# Standard に変更
aws s3 cp s3://$bucket/example.html s3://$bucket/example.html \
  --storage-class STANDARD

CloudWatch Logs 確認

EventBridge Rule のターゲットに設定した CloudWatch Logs を確認します。 "logStreams": [] が表示されるはずです。

aws logs describe-log-streams \
  --log-group-name $log_group_name \
  --order-by LastEventTime \
  --descending

{
  "logStreams": []
}

(補足) アップロード確認

念のため、オブジェクトのアップロード (PutObject) が EventBridge Rule で処理されることを確認しておきます。

# Upload
aws s3 cp example.html s3://$bucket/example-new.html

# CloudWatch Logs 確認
log_stream_name=$(
  aws logs describe-log-streams \
    --log-group-name $log_group_name \
    --order-by LastEventTime \
    --descending \
  | jq -r '.logStreams[0].logStreamName'
)

aws logs get-log-events \
  --log-group-name $log_group_name \
  --log-stream-name $log_stream_name \
| jq '.events[].message | fromjson'

"reason": "PutObject" のイベントは、EventBridge Rule で処理されていることが確認できます。

{
  "version": "0",
  "id": "f54bb5b7-e02e-da2a-b89c-6fb667139950",
  "detail-type": "Object Created",
  "source": "aws.s3",
  "account": "123456789012",
  "time": "2024-11-24T09:59:39Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:s3:::storage-class-event-test-<UUID>"
  ],
  "detail": {
    "version": "0",
    "bucket": {
      "name": "storage-class-event-test-<UUID>"
    },
    "object": {
      "key": "example-new.html",
      "size": 136162,
      "etag": "2fb8b1f49c6b495c614f1f7a7efb8835",
      "sequencer": "006742F90B0B6E1613"
    },
    "request-id": "MM7A7QD53JB1WFC5",
    "requester": "123456789012",
    "source-ip-address": "xxx.xxx.xxx.xxx",
    "reason": "PutObject"
  }
}

まとめ

S3 バケットにイベント通知を設定している場合、ストレージクラスを変更すると、予期せぬイベントを処理してしまう可能性があります。

次の条件に該当する場合、Event Pattern に reason が含まれているか確認することをお勧めします。

  • ストレージクラスの変更時に発生するイベントを処理した場合、データの不整合が生じる。(冪等性を考慮した設計になっていない)
  • 膨大な数のオブジェクトが、ストレージクラスの変更対象になる。
  • Object Created - CopyObject イベントを処理する必要が無い。

以上、お役に立てれば幸いです。