Gemcook Tech Blog
🦟

S3の不完全なマルチパートアップロードを自動削除する【CloudFormation・CDK】

に公開

こんにちは、村中(@wage790)です。

S3の費用削減施策の一つに、バケットに存在する「不完全なマルチパートアップロード」を削除する方法があります。これが残っていると、ストレージ料金が発生します。

今回自分は、S3のライフサイクルルールを活用し、指定した日数が経過した後に対象のS3バケット内にある不完全なマルチパートアップロードを自動で削除する設定を公式ドキュメント[1]に沿いながら行いました。

自分はCloudFormationで設定しましたが、CDKを使う場合の設定方法も気になったため、CloudFormationとCDKそれぞれで設定する方法について備忘録としてまとめます。

マルチパートアップロードについて

マルチパートアップロードとは、大きなファイルを「複数(マルチ)」の「小さな部品(パート)」に分けて、S3にアップロードする仕組みです。

各パートを並列でアップロードできるため、アップロード時間の短縮や、ネットワーク障害時の耐性向上といったメリットがあります。

ただし、マルチパートアップロードが何らかの理由[2]で中断されると、不完全なパートがS3に残り続けてしまう点には注意が必要です。これらの不完全なパートにも料金が発生するため、削除や中止の設定を行うことが推奨されています。

設定方法

設定方法は簡単で、S3バケットのライフサイクル設定に不完全なマルチパートアップロードの削除ルールを指定するだけです。

基本的な設定例

CloudFormationの場合

CloudFormationでは、S3バケットのLifecycleConfigurationプロパティのRules内にAbortIncompleteMultipartUploadを指定します。

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-bucket
      LifecycleConfiguration:
        Rules:
          - Status: Enabled
            # 不完全なマルチパートアップロードの削除設定を追加
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7

CDKの場合

CDKでは、S3バケットのlifecycleRulesプロパティにabortIncompleteMultipartUploadAfterを指定します。

import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

export class MyS3Stack extends Construct {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const bucket = new s3.Bucket(this, 'MyS3Bucket', {
      bucketName: 'my-bucket',
      lifecycleRules: [
        {
          enabled: true,
          // 不完全なマルチパートアップロードの削除設定を追加
          abortIncompleteMultipartUploadAfter: s3.Duration.days(7),
        },
      ],
    });
  }
}

DaysAfterInitiation(CloudFormation)およびabortIncompleteMultipartUploadAfter(CDK)は、マルチパートアップロードの開始から削除までの猶予日数を示します。指定された日数内に完了しなかった場合、そのアップロードは自動的に中止され、関連するすべてのパートが削除されます。

なお、削除対象となるのは未完了(不完全)のアップロードのみであり、完了済みのマルチパートアップロードには影響しません。

複数のライフサイクルルールがある場合

複数のルールがある場合は、それぞれに設定することも可能です。

CloudFormationの場合

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-bucket
      LifecycleConfiguration:
        Rules:
          - Status: Enabled
            Prefix: logs/
            ExpirationInDays: 30
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
          - Status: Enabled
            Prefix: exports/
            ExpirationInDays: 90
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7

CDKの場合

export class MyS3Stack extends Construct {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const bucket = new s3.Bucket(this, 'MyS3Bucket', {
      bucketName: 'my-bucket',
      lifecycleRules: [
        {
          enabled: true,
          prefix: 'logs/',
          expiration: s3.Duration.days(30),
          abortIncompleteMultipartUploadAfter: s3.Duration.days(7),
        },
        {
          enabled: true,
          prefix: 'exports/',
          expiration: s3.Duration.days(90),
          abortIncompleteMultipartUploadAfter: s3.Duration.days(7),
        },
      ],
    });
  }
}

プレフィックスとトランジションを指定した設定

特定のプレフィックスを持つオブジェクトのみに適用することもできます。

CloudFormationの場合

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-bucket
      LifecycleConfiguration:
        Rules:
          - Status: Enabled
            Prefix: data/
            ExpirationInDays: 365
            Transitions:
              - Days: 30
                StorageClass: STANDARD_IA
              - Days: 90
                StorageClass: GLACIER
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7

CDKの場合

export class MyS3Stack extends Construct {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const bucket = new s3.Bucket(this, 'MyS3Bucket', {
      bucketName: 'my-bucket',
      lifecycleRules: [
        {
          enabled: true,
          prefix: 'data/',
          expiration: s3.Duration.days(365),
          transitions: [
            {
              storageClass: s3.StorageClass.INFREQUENT_ACCESS,
              transitionAfter: s3.Duration.days(30),
            },
            {
              storageClass: s3.StorageClass.GLACIER,
              transitionAfter: s3.Duration.days(90),
            },
          ],
          abortIncompleteMultipartUploadAfter: s3.Duration.days(7),
        },
      ],
    });
  }
}

デプロイと確認

設定完了後は、CloudFormationまたはCDKを使用してインフラストラクチャをデプロイし、設定が正しく反映されているかを確認します。

CloudFormationでのデプロイ

# dry-run
aws cloudformation deploy \
  --stack-name MyStack \
  --template-file template.yml \
  --no-execute-changeset

# execute
aws cloudformation deploy \
  --stack-name MyStack \
  --template-file template.yml

CDKでのデプロイ

# 差分確認
cdk diff MyS3Stack

# デプロイ
cdk deploy MyS3Stack

設定確認

デプロイ後、AWS CLIで設定が正しく反映されているか確認します。

CloudFormationもCDKも、内部的には同じS3のライフサイクル設定APIを使用してバケットに設定を適用するため、確認手順は共通です。

# 設定確認
aws s3api get-bucket-lifecycle-configuration \
  --bucket my-bucket \
  --query 'Rules[].AbortIncompleteMultipartUpload'

{"DaysAfterInitiation": 7}が出力されれば問題なく設定できています。

以上で設定完了です!

最後に

最近S3の料金が高いと感じる場合は、不完全なマルチパートアップロードのオブジェクトがどれくらい存在するかを確認し、ライフサイクル設定を見直してみてはいかがでしょうか。

どなたかの参考になれば幸いです!

脚注
  1. 不完全なマルチパートアップロードを削除するためのバケットライフサイクル設定の設定 - Amazon Simple Storage Service ↩︎

  2. たとえば、アップロードに長時間かかってタイムアウトしたり、アップロード中にブラウザやターミナルを閉じてクライアントが異常終了したりすると、マルチパートアップロードは途中で停止し、S3には不完全なパートだけが残ります。 ↩︎

Gemcook Tech Blog
Gemcook Tech Blog

Discussion