Amazon S3 объединяет небольшие файлы
Есть ли способ объединить небольшие файлы размером менее 5 МБ на Amazon S3. Multi-Part Upload не подходит из-за небольших файлов.
Это не эффективное решение, чтобы снять все эти файлы и выполнить конкатенацию.
Итак, кто-нибудь может сказать мне некоторые API для этого?
2 ответа
Amazon S3 не предоставляет функции сцепления. Это прежде всего сервис хранения объектов.
Вам понадобится некоторый процесс, который загружает объекты, объединяет их, а затем загружает их снова. Наиболее эффективный способ сделать это - загрузить объекты параллельно, чтобы в полной мере использовать доступную пропускную способность. Однако это более сложный код.
Я бы порекомендовал выполнять обработку "в облаке", чтобы избежать необходимости загружать объекты через Интернет. Выполнение этого на Amazon EC2 или AWS Lambda будет более эффективным и менее затратным.
Основываясь на комментарии @wwadge, я написал скрипт Python.
Он обходит ограничение в 5 МБ, загружая фиктивный объект размером чуть больше 5 МБ, а затем добавляя каждый небольшой файл, как если бы он был последним. В конце концов, он удаляет фиктивную часть из объединенного файла.
import boto3
import os
bucket_name = 'multipart-bucket'
merged_key = 'merged.json'
mini_file_0 = 'base_0.json'
mini_file_1 = 'base_1.json'
dummy_file = 'dummy_file'
s3_client = boto3.client('s3')
s3_resource = boto3.resource('s3')
# we need to have a garbage/dummy file with size > 5MB
# so we create and upload this
# this key will also be the key of final merged file
with open(dummy_file, 'wb') as f:
# slightly > 5MB
f.seek(1024 * 5200)
f.write(b'0')
with open(dummy_file, 'rb') as f:
s3_client.upload_fileobj(f, bucket_name, merged_key)
os.remove(dummy_file)
# get the number of bytes of the garbage/dummy-file
# needed to strip out these garbage/dummy bytes from the final merged file
bytes_garbage = s3_resource.Object(bucket_name, merged_key).content_length
# for each small file you want to concat
# when this loop have finished merged.json will contain
# (merged.json + base_0.json + base_2.json)
for key_mini_file in ['base_0.json','base_1.json']: # include more files if you want
# initiate multipart upload with merged.json object as target
mpu = s3_client.create_multipart_upload(Bucket=bucket_name, Key=merged_key)
part_responses = []
# perform multipart copy where merged.json is the first part
# and the small file is the second part
for n, copy_key in enumerate([merged_key, key_mini_file]):
part_number = n + 1
copy_response = s3_client.upload_part_copy(
Bucket=bucket_name,
CopySource={'Bucket': bucket_name, 'Key': copy_key},
Key=merged_key,
PartNumber=part_number,
UploadId=mpu['UploadId']
)
part_responses.append(
{'ETag':copy_response['CopyPartResult']['ETag'], 'PartNumber':part_number}
)
# complete the multipart upload
# content of merged will now be merged.json + mini file
response = s3_client.complete_multipart_upload(
Bucket=bucket_name,
Key=merged_key,
MultipartUpload={'Parts': part_responses},
UploadId=mpu['UploadId']
)
# get the number of bytes from the final merged file
bytes_merged = s3_resource.Object(bucket_name, merged_key).content_length
# initiate a new multipart upload
mpu = s3_client.create_multipart_upload(Bucket=bucket_name, Key=merged_key)
# do a single copy from the merged file specifying byte range where the
# dummy/garbage bytes are excluded
response = s3_client.upload_part_copy(
Bucket=bucket_name,
CopySource={'Bucket': bucket_name, 'Key': merged_key},
Key=merged_key,
PartNumber=1,
UploadId=mpu['UploadId'],
CopySourceRange='bytes={}-{}'.format(bytes_garbage, bytes_merged-1)
)
# complete the multipart upload
# after this step the merged.json will contain (base_0.json + base_2.json)
response = s3_client.complete_multipart_upload(
Bucket=bucket_name,
Key=merged_key,
MultipartUpload={'Parts': [
{'ETag':response['CopyPartResult']['ETag'], 'PartNumber':1}
]},
UploadId=mpu['UploadId']
)
Если у вас уже есть объект размером>5 МБ, к которому вы хотите добавить также более мелкие части, пропустите создание фиктивного файла и последней копии части с байтовыми диапазонами. Кроме того, я понятия не имею, как это работает с большим количеством очень маленьких файлов - в этом случае может быть лучше загрузить каждый файл, объединить их локально и затем загрузить.
Изменить: не видел требования 5 МБ. Этот метод не будет работать из-за этого требования.
Хотя можно загружать и повторно загружать данные в S3 через экземпляр EC2, более эффективным подходом было бы дать S3 указание сделать внутреннюю копию с помощью новой операции API copy_part, которая была введена в SDK для Ruby в версии 1.10. +0,0.
Код:
require 'rubygems'
require 'aws-sdk'
s3 = AWS::S3.new()
mybucket = s3.buckets['my-multipart']
# First, let's start the Multipart Upload
obj_aggregate = mybucket.objects['aggregate'].multipart_upload
# Then we will copy into the Multipart Upload all of the objects in a certain S3 directory.
mybucket.objects.with_prefix('parts/').each do |source_object|
# Skip the directory object
unless (source_object.key == 'parts/')
# Note that this section is thread-safe and could greatly benefit from parallel execution.
obj_aggregate.copy_part(source_object.bucket.name + '/' + source_object.key)
end
end
obj_completed = obj_aggregate.complete()
# Generate a signed URL to enable a trusted browser to access the new object without authenticating.
puts obj_completed.url_for(:read)
Ограничения (среди прочих)
- За исключением последней части, минимальный размер составляет 5 МБ.
- Максимальный размер заполненного объекта Multipart Upload составляет 5 ТБ.