카테고리 없음

[python,Flask]임시 파일 생성 및 처리 방식 비교

Grapefruitgreentealoe 2025. 2. 10. 15:21
반응형

파일을 생성하고 반환하는 방식은 웹 애플리케이션에서 자주 사용되는 패턴입니다. 현재 사용하고 있는 방식은 tempfile.mkdtemp()를 사용하여 임시 디렉토리를 만들고, 해당 디렉토리에 파일을 저장한 후 반환하는 구조입니다. 이 방법이 일반적으로 사용되는지, 그리고 대체 방법과 비교했을 때의 장단점을 정리해보겠습니다.


1. 현재 방식 (tempfile.mkdtemp())

장점

  • 보안성 확보
    • 임시 디렉토리를 생성하여 외부 접근을 방지할 수 있음.
    • OS가 제공하는 임시 저장소를 사용하므로, 별도의 관리가 필요 없음.
  • 다중 사용자 처리 가능
    • 각 요청마다 별도의 디렉토리를 생성하므로, 여러 사용자가 동시에 요청해도 파일이 충돌할 가능성이 낮음.
  • 자동 삭제 가능
    • @after_this_request를 활용하여 응답이 끝난 후 파일을 삭제할 수 있음.
  • 유연한 확장 가능
    • 현재 로직을 조금 수정하면 여러 종류의 파일을 유동적으로 생성할 수 있음.

단점

  • 디스크 I/O 부하 발생 가능
    • 요청이 많아지면 임시 디렉토리와 파일을 계속 생성/삭제해야 하므로 디스크 I/O가 증가할 수 있음.
  • 파일 삭제 실패 가능성
    • @after_this_request를 사용하지만, 파일이 삭제되기 전에 서버가 중지되거나 오류가 발생하면 파일이 남을 가능성이 있음.
  • Docker 컨테이너 환경에서 주의 필요
    • 컨테이너 내부의 /tmp 디렉토리를 사용할 경우, 컨테이너가 삭제되면 파일도 사라지지만, 파일 정리가 안 되면 공간을 계속 차지할 수 있음.

2. 대체 방법

메모리 기반 파일 처리 (io.BytesIO)

io.BytesIO()를 활용하면 파일을 디스크에 저장하지 않고 메모리에서 직접 처리할 수 있습니다.

import io
from flask import send_file

@app.route('/generate-file/<file_id>', methods=['GET'])
def generate_pdf_api(file_id):
    csv_path = get_csv_from_s3(file_id)
    if not csv_path:
        return jsonify({"error": "CSV file not found"}), 404
    
    pdf_buffer = generate_excel_pdf_in_memory(csv_path)
    if pdf_buffer is None:
        return jsonify({"error": "PDF file was not generated"}), 500

    return send_file(io.BytesIO(pdf_buffer), as_attachment=True, download_name="output.pdf", mimetype="application/pdf")
 
장점
  • 디스크 I/O 부담 없음 → 파일을 생성할 필요 없이 메모리에서 바로 응답 가능.
  • 속도 향상 → 작은 파일의 경우, 디스크에서 읽고 쓰는 속도보다 훨씬 빠름.
  • 파일 삭제 관리가 필요 없음 → 파일을 저장하지 않으므로 @after_this_request로 파일을 삭제할 필요가 없음.

단점

  • 메모리 사용량 증가 → 큰 파일을 생성할 경우, 서버 메모리를 과다하게 사용할 가능성이 있음.
  • 재사용이 어려움 → 파일을 임시로 저장하지 않으므로, 다른 API에서 동일한 파일을 다시 사용하려면 다시 생성해야 함.

비동기 작업을 활용한 저장 방식 (Celery + Redis)

Flask 앱이 직접 파일을 생성하고 응답하는 대신, 비동기 작업 큐(Celery + Redis)를 활용하여 파일을 백그라운드에서 생성하고, 완료되면 사용자가 다운로드할 수 있도록 처리할 수도 있습니다.

from celery import Celery
from flask import jsonify

celery = Celery('tasks', broker='redis://localhost:6379/0')

@celery.task
def generate_pdf_task(file_id):
    csv_path = get_csv_from_s3(file_id)
    if not csv_path:
        return None
    
    pdf_path = generate_excel_pdf(csv_path)
    return pdf_path

@app.route('/generate-file/<file_id>', methods=['GET'])
def generate_pdf_api(file_id):
    task = generate_pdf_task.delay(file_id)
    return jsonify({"task_id": task.id}), 202

@app.route('/get-file/<task_id>', methods=['GET'])
def get_file(task_id):
    task = generate_pdf_task.AsyncResult(task_id)
    if task.state == 'SUCCESS':
        return send_file(task.result, as_attachment=True, download_name="output.pdf", mimetype="application/pdf")
    elif task.state == 'PENDING':
        return jsonify({"status": "Processing"}), 202
    else:
        return jsonify({"error": "File generation failed"}), 500
 

장점

  • 비동기 처리 가능 → 서버가 파일을 바로 생성하는 것이 아니라, 백그라운드에서 처리한 후 사용자가 나중에 받을 수 있음.
  • 확장성 뛰어남 → 사용자가 많아질 경우에도 Redis와 Celery를 활용해 로드 밸런싱 가능.
  • 서버 부하 감소 → API 서버는 요청을 바로 응답하고, 파일 생성은 별도의 백그라운드 작업으로 수행됨.

단점

  • 설치 및 관리 필요 → Celery와 Redis 설정이 필요하며, 운영이 복잡해질 수 있음.
  • 즉시 다운로드 불가 → 사용자는 파일을 기다려야 하며, 다운로드 링크를 따로 받아야 함.

S3에 직접 저장 후 URL 반환

파일을 로컬에 저장하지 않고, AWS S3 같은 외부 스토리지에 직접 업로드한 후 URL을 반환할 수도 있습니다.

import boto3
from flask import jsonify

s3_client = boto3.client('s3')

def upload_to_s3(file_path, bucket_name, key):
    s3_client.upload_file(file_path, bucket_name, key)
    return f"https://{bucket_name}.s3.amazonaws.com/{key}"

@app.route('/generate-file/<file_id>', methods=['GET'])
def generate_pdf_api(file_id):
    csv_path = get_csv_from_s3(file_id)
    if not csv_path:
        return jsonify({"error": "CSV file not found"}), 404

    pdf_path = generate_excel_pdf(csv_path)
    if pdf_path is None:
        return jsonify({"error": "PDF file was not generated"}), 500

    file_url = upload_to_s3(pdf_path, "my-bucket", f"generated_pdfs/{file_id}.pdf")
    return jsonify({"file_url": file_url})

장점

  • 파일 저장 관리가 필요 없음 → 로컬 파일을 저장하고 삭제할 필요 없이 S3에서 관리 가능.
  • 확장성 뛰어남 → 클라우드 저장소를 사용하므로 파일 크기가 커져도 문제 없음.
  • 비용 효율적 → 서버 스토리지를 차지하지 않고, S3에서 비용 효율적으로 저장 가능.

단점

  • S3 설정 필요 → AWS 계정이 필요하고, 버킷 및 권한을 설정해야 함.
  • 업로드 속도 영향 → 인터넷 업로드 속도에 따라 파일이 저장되는 시간이 달라질 수 있음.

3. 정리 및 결론

 

방법 장점 단점 추천 상황
tempfile.mkdtemp() 보안성 높음, 다중 사용자 처리 가능 디스크 I/O 부하, 삭제 실패 가능성 일반적인 Flask API에서 임시 파일 생성
io.BytesIO() 디스크 I/O 없음, 속도 빠름 큰 파일에서 메모리 부담 작은 파일을 빠르게 반환할 때
Celery + Redis 비동기 처리 가능, 서버 부하 감소 설정이 복잡, 즉시 다운로드 불가 대량 요청을 처리해야 하는 경우
S3 저장 후 URL 반환 스토리지 관리 불필요, 확장성 뛰어남 S3 설정 필요, 업로드 속도 영향 클라우드 환경에서 장기 보관이 필요할 때

 

현재 방법(tempfile.mkdtemp())도 널리 사용되지만, 파일 크기와 요청량이 증가할 경우 io.BytesIO() 또는 S3 저장 방식을 고려하는 것이 좋습니다.

반응형