청크는 대용량 파일을 작고 관리하기 쉬운 조각으로 나눈 것입니다.
일반적으로 1MB에서 5MB 사이의 크기로 나누지만, 네트워크 상태와 서버 설정에 따라 조절할 수 있습니다.
각 청크는 고유 식별자를 가지며, 이를 통해 서버는 청크의 순서와 완전성을 확인합니다.
게이트웨이 제한 우회:
대부분의 서버와 게이트웨이는 10MB 이상의 데이터 전송을 제한합니다.
청크 업로드를 사용하면 이 제한을 우회하여 수백 MB 또는 GB 단위의 대용량 파일도 전송할 수 있습니다.
네트워크 중단에 대한 복원력:
업로드 중 연결이 끊어져도 이미 전송된 청크는 서버에 유지됩니다.
연결이 복구되면 마지막으로 성공한 청크부터 업로드를 재개할 수 있습니다.
리소스 최적화:
병렬 처리 가능:
프론트엔드:
파일을 업로드 받습니다.
해당 파일을 지정된 크기(예: 5MB)의 청크로 나눕니다.
각 청크에 대해 FormData 객체를 생성하고 청크 데이터와 메타데이터를 추가합니다.
반복적으로 axios POST 요청을 보내 청크들을 하나씩 전달합니다.
이 때, 청크의 총 갯수와 현재 전송하고 있는 청크의 인덱스 번호를 전달합니다.
백엔드:
해당 엔드포인트로 클라이언트가 전달한 청크 데이터와 메타데이터를 받습니다.
받은 청크를 임시 저장소(디렉토리, 전역 변수 등)에 저장합니다.
청크를 받아서 S3에 임시로 저장합니다.
청크 데이터가 모두 모이게 되면 하나의 파일 데이터로 바꿔서 해당 파일에 대한 URL를 반환합니다.
async function uploadFile(file) {
const chunkSize = 5 * 1024 * 1024; // 5MB
const totalChunks = Math.ceil(file.size / chunkSize);
for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
const start = (chunkNumber - 1) * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk, file.name);
formData.append('filename', file.name);
formData.append('chunk_number', chunkNumber);
formData.append('total_chunks', totalChunks);
try {
const response = await axios.post('/upload-chunk/', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
console.log(response.data.message);
if (chunkNumber === totalChunks) {
console.log('파일 업로드 완료. URL:', response.data.file_url);
// 여기서 file_url을 사용하여 추가 작업을 수행할 수 있습니다.
}
} catch (error) {
console.error('업로드 실패:', error);
break; // 에러 발생 시 업로드 중단
}
}
}
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.responses import JSONResponse
import boto3
from botocore.exceptions import ClientError
app = FastAPI()
s3_client = boto3.client('s3',
aws_access_key_id='YOUR_ACCESS_KEY',
aws_secret_access_key='YOUR_SECRET_KEY',
region_name='YOUR_REGION'
)
BUCKET_NAME = 'YOUR_BUCKET_NAME'
S3_URL_PREFIX = f"https://{BUCKET_NAME}.s3.amazonaws.com/"
multipart_uploads = {}
@app.post("/upload-chunk/")
async def upload_chunk(
file: UploadFile = File(...),
filename: str = Form(...),
chunk_number: int = Form(...),
total_chunks: int = Form(...)
):
try:
if filename not in multipart_uploads:
response = s3_client.create_multipart_upload(Bucket=BUCKET_NAME, Key=filename)
multipart_uploads[filename] = {
"UploadId": response["UploadId"],
"Parts": []
}
upload_id = multipart_uploads[filename]["UploadId"]
response = s3_client.upload_part(
Bucket=BUCKET_NAME,
Key=filename,
PartNumber=chunk_number,
UploadId=upload_id,
Body=file.file
)
multipart_uploads[filename]["Parts"].append({
"PartNumber": chunk_number,
"ETag": response["ETag"]
})
if chunk_number == total_chunks:
complete_response = s3_client.complete_multipart_upload(
Bucket=BUCKET_NAME,
Key=filename,
UploadId=upload_id,
MultipartUpload={"Parts": sorted(multipart_uploads[filename]["Parts"], key=lambda x: x["PartNumber"])}
)
del multipart_uploads[filename]
# S3 URL 생성
file_url = f"{S3_URL_PREFIX}{filename}"
return JSONResponse(content={
"message": "파일 업로드 완료",
"file_url": file_url
}, status_code=200)
return JSONResponse(content={"message": f"청크 {chunk_number}/{total_chunks} 업로드 성공"}, status_code=200)
except ClientError as e:
return JSONResponse(content={"message": f"업로드 실패: {str(e)}"}, status_code=500)
의존성 주입(Dependency Injection) 심화 학습
FastAPI의 보안 기능 (인증, 권한 관리) 탐구
대용량 파일 처리의 복잡성을 직접 경험하며 효율적인 업로드 방식의 중요성을 깨달았습니다.
청크 단위 업로드를 통해 네트워크 불안정성에 대응하는 방법을 학습했습니다. 이는 실제 서비스에서 매우 중요한 부분이라고 느꼈습니다.
S3와 FastAPI를 연동하는 과정에서 클라우드 서비스와 웹 프레임워크의 통합에 대한 이해도가 높아졌습니다.
단일 엔드포인트에서 여러 기능을 처리하는 방식을 구현하며, API 설계의 유연성에 대해 고민해볼 수 있었습니다.