File Chunk Upload란 파일을 분할로 업로드한다는 의미이다.
File Chunk Upload 의 개념을 AWS 에 적용한다면 File Multipart Upload 으로 사용할 수 있다.
비교적 용량이 큰 파일들을 업로드 해야하는 상황이 있다
대용량의 파일을 업로드 할 경우에는 클라이언트에서 업로드가 끝날 때까지 묵묵히 기다리는 수밖에는 없다
즉, 진행과정을 알 수 없었던 부분을 해결하기 위해 파일이 얼만큼 올라갔는지 사용자에게 보여주는 방법이 필요하다고 생각했다
File Chunk Upload를 왜 해야할까?
파일을 업로드하면서 단일 요청에 대한 값으로서 ‘그냥 업로드 요청이 잘 들어오고 잘 업로드하면 되는’ 수준으로 생각했는데, 용량이 커지면서 종종 뭔가 더 효율적인 방법이 있을 것 이라고 생각했다
파일을 업로드 하는 상황은 주로 아래와 같다
(큰 범주에서 다음과 같은 상황들에서 업로드가 이뤄진다)
- django admin 에서 파일을 업로드(File Field)
- API를 통해서 파일을 업로드(Form Data)
- (운영환경의 경우 AWS S3 를 쓰다보니 presigned url 을 사용하는게 더 효율적이다)
그러던 중 chunk upload, (S3에서는) multi-part upload 의 개념을 적용했다
chunk는 파일을 나눈 단위로 언급(?) 되는데
예를 들어 1GB 의 파일을 업로드할 때 chunk를 100MB 단위로 설정하면 총 10개의 chunk 가 업로드 되는 것 이다
File Chunk Upload 업로드 방식
파일의 관점에서 볼 때 스토리지에 저장하는 로직을 크게 아래 3가지로 정리 할 수 있다
- File Upload 1 : django 내부의 media 루트에 저장
- WAS를 스토리지로 쓰는 것은 제일 좋지 않은 방식 같다
- File Upload 2: frontend -> django 를 통해서 AWS S3에 저장
- 1번과 비교했을 때 frontend 입장에서 달라지는 것은 없다 하지만 backend는 S3로 미디어가 저장되도록 세팅 해야한다
- (다음 글에서 설명)File Upload 3: (presigned_url을 이용해)frontend 에서 AWS S3에 저장
직관적으로 생각해도 파일을 업로드하는데 (django)서버를 거치지 않고 업로드 하는 것보다,
바로 스토리지로 업로드 하는 것이 더 효율적이다
각 관점에서 파일을 분할해서 업로드 해보려고 한다
File Chunk Upload : File Upload 1 → local chunk upload
model example
class FileUpload(BaseModel):
file = models.FileField(upload_to="uploads/")
file_name = models.CharField(max_length=255)
uploaded_percentage = models.FloatField(default=0)
status = models.CharField(max_length=50, default="uploading")
create example
# serializer (view 단 이어도 비슷하다)
def create(self, validated_data):
uploaded_file = validated_data.get("file")
return handle_uploaded_file(uploaded_file)
main funcition
def handle_uploaded_file(uploaded_file):
chunk_size = 5 * 1024 * 1024 # 5mb 이다
total_size = uploaded_file.size
uploaded_size = 0
unique_filename = uploaded_file.name
file_upload, created = FileUpload.objects.get_or_create(file_name=unique_filename)
# with open(file_upload.file.path, "wb") as destination:
with open(
f"경로!!!/uploads/{unique_filename}", "wb"
) as destination:
for chunk in uploaded_file.chunks(chunk_size):
destination.write(chunk)
uploaded_size += len(chunk)
# 업로드 진행률 계산 및 데이터베이스 업데이트
uploaded_percentage = (uploaded_size / total_size) * 100
file_upload.uploaded_percentage = uploaded_percentage
file_upload.save()
print(f"청크:{uploaded_percentage}%")
file_upload.file = f"uploads/{unique_filename}"
file_upload.status = "completed"
file_upload.save()
return file_upload
chunk를 5mb단위로 끊는다
즉 파일을 5mb단위로 나눠서 업로드 하겠다는 것이다
front end 입장에서 API polling으로 file_upload의 percentage를 받아서 쓴다면 업로드 진행상황을 추적가능해, needs는 충족이 된다
현재 로직의 단점
- local app 에 저장이 된다
- chunk 만큼 트랜잭션이 발생한다는 것.. 서비스가 커진다면 최악인 것이고, 서비스가 커지지 않아도 효율적인 것과 거리가 멀다
그래도 장점이라고 한다면 얼만큼 올라가는지에 대해서는 확실히 알 수 있다
File Chunk Upload : 2 → S3 multipart-upload(To real bucket in function)
Front로 파일이 업로드 될 때 S3에서 제공하는 multipart upload를 이용해 업로드 하는 방식이다
import math
import boto3
from rest_framework import serializers
from app.file_upload.models import FileUpload
class FileUploadSerializer(serializers.ModelSerializer):
class Meta:
model = FileUpload
fields = [
"id",
"file",
"file_name",
"uploaded_percentage",
"status",
]
def validate(self, attrs):
attrs = super().validate(attrs)
return attrs
def create(self, validated_data):
file_obj = validated_data.get("file")
bucket_name = "taktoon-dev-bucket"
print("file_obj", file_obj)
print("file_obj", type(file_obj))
# S3 클라이언트 생성
s3 = boto3.client("s3")
try:
# Multipart 업로드 시작
mp = s3.create_multipart_upload(Bucket=bucket_name, Key=file_obj.name)
upload_id = mp["UploadId"]
part_info = {"Parts": []}
chunk_size = 5 * 1024 * 1024 # 5MB
file_size = file_obj.size
chunk_count = int(math.ceil(file_size / float(chunk_size)))
for i in range(chunk_count):
part_num = i + 1
# 파일의 각 청크를 읽고 업로드
chunk = file_obj.read(chunk_size)
part = s3.upload_part(
Bucket=bucket_name, Key=file_obj.name, UploadId=upload_id, PartNumber=part_num, Body=chunk
)
part_info["Parts"].append({"PartNumber": part_num, "ETag": part["ETag"]})
# 진행상황 출력
print(f"Part {part_num}/{chunk_count} uploaded, {(part_num / chunk_count) * 100}% completed")
# Multipart 업로드 완료
s3.complete_multipart_upload(
Bucket=bucket_name, Key=file_obj.name, UploadId=upload_id, MultipartUpload=part_info
)
except Exception as e:
# 에러 발생 시 Multipart 업로드 중단
s3.abort_multipart_upload(Bucket=bucket_name, Key=file_obj.name, UploadId=upload_id)
print(e)
instance = super().create(validated_data)
return instance
def update(self, instance, validated_data):
instance = super().update(instance, validated_data)
return instance
S3멀티파트 업로드 개념
Multipart 업로드는 S3에서 제공하는 대용량 파일을 여러 부분으로 나누어 업로드하는 프로세스이다
- 이 청크들은 일시적으로 S3 버킷에 저장되며, 아직 하나의 파일로 합쳐지지 않은 상태이다
- 만약 업로드의 미완료 처리에 대하여
- 미완료 청크들은 일시적으로 해당 S3 버킷에 저장된다 (일반적으로 사용자나 애플리케이션에서 접근할 수 없다)
- multipart 업로드가 완료되지 않을 경우, 청크들은 일정 시간 후(보통 24시간 이내)에 S3에 의해 자동 삭제된다
- 만약 abort_multipart_upload를 사용하여 미완료 업로드를 중단하고 미완료 청크를 명시적으로 삭제할 수 있다
- 업로드 완료 (complete_multipart_upload)를 왜 호출할까?
- 모든 청크가 업로드되면, complete_multipart_upload 호출이 되는데, S3에게 모든 청크를 하나의 파일로 결합하도록 지시하는 역할을 한다
- 결합이 완료된 파일은 버킷에서 일반적으로 접근할 수 있는 객체로 완성이 된다
레퍼런스
- [S3 Multipart Upload]https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/mpuoverview.html
'Develop' 카테고리의 다른 글
[AWS] RDS 및 Aurora SSL/TLS C인증서 업데이트 및 자동교체여부 확인하기 (0) | 2023.10.18 |
---|---|
[Django] refresh_from_db() : 메모리 상의 Django 객체와 데이터베이스 동기화하기 (0) | 2023.10.06 |
[Django] webp ,바이트 스트림(Byte Stream) 개념과 이미지 webp 형식으로 변환하기 (0) | 2023.09.01 |
[AWS] CloudFormation : 문법 및 스택정책을 쉽게 이해하기 (0) | 2023.08.27 |
[AWS]Lambda 기초 정리 및 AWS CLI로 확인, ALB<->Lambda 연결 (0) | 2023.08.22 |