patorashのブログ

方向性はまだない

AWS SDK for Rubyでminio上のバケットを削除するときのTips

自分のための備忘録です。

seed-fuを使ってデータ投入していたのだが、データを作り直そうと思ってseed-fuを再び実行したところ、minioのバケットを作るところでコケた。原因は、既にバケットがあったからだった。

require 'aws-sdk-s3'

storage_yml = YAML.safe_load(ERB.new(File.read(Rails.root.join('config', 'storage.yml'))).result).fetch('minio')
bucket_name = storage_yml.delete('bucket')

# 既にバケットがある場合はNGだった…
Aws::S3::Client.new(
  access_key_id: storage_yml['access_key_id'],
  secret_access_key: storage_yml['secret_access_key'],
  region: storage_yml['region'],
  endpoint: storage_yml['endpoint'],
  force_path_style: storage_yml['force_path_style']
).tap { |s3_client| s3_client.create_bucket(bucket: bucket_name) }

なので、AWS SDK for Rubyを使ってバケットを削除しようとしたのだが、またコケた…。バケットにファイルが残っていたらバケットは消せないらしい。ファイルごと消せるかと思いきや、現時点ではそんなオプションはなかった。

# 省略
).tap do |s3_client|
  # バケットにファイルがある場合は削除できず…
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

なので!バケットにあるファイルを全部削除してからバケットを削除しようとしたのだが、またコケた!

# 省略
).tap do |s3_client|
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    # バケット内のファイルを削除
    s3_client.list_objects(bucket: bucket_name).contents.each do |object|
      s3_client.delete_object(bucket: bucket_name, key: object.key)
    end
    # 何故かまだバケットが削除できない!?
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

削除できない原因

原因はシンプルで、まだバケットにファイルが残っていたからだった。list_objectsメソッドで取得できるオブジェクトの数の上限が1,000だったので、1,000個以上ファイルがバケットにある場合は消しきれない。ということは、ファイルを全部消すまでループするようにしなければならない。

削除できるように直す

最初の実装

シンプルにこうした。

# 省略
).tap do |s3_client|
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    # コンテンツが有る限り削除し続ける
    while s3_client.list_objects(bucket: bucket_name).contents.present?
      s3_client.list_objects(bucket: bucket_name).contents.each do |object|
        s3_client.delete_object(bucket: bucket_name, key: object.key)
      end
    end
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

しかし、これだとファイルが大量にある場合はminioに都度ファイルの有無を確認する通信が発生するので、ちょっとダサいなと感じた。

リファクタリング

begin .. end whileを使って書き直した。これならば、minioに通信する回数は最小限となって効率がよい。

# 省略
).tap do |s3_client|
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    contents = nil
    begin
      contents&.each do |object|
        s3_client.delete_object(bucket: bucket_name, key: object.key)
      end
      contents = s3_client.list_objects(bucket: bucket_name).contents
    end while contents.present?
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

まとめ

  • バケットを消すにはファイルを全削除しなければならない
  • list_objectsメソッドは1,000件しか取れないので注意
  • AWS CLIだったらファイルごとバケットを削除可能らしい(同僚に教えてもらった)

バケットを削除するか空にする - Amazon Simple Storage Service

ちなみにminioでAWS CLIを使う時の情報はこちら

MinIO | AWS CLI with MinIO - Cookbook/Recipe