웹 워커를 활용한 대용량 파일 업로드 구현 방법

0

대용량 파일을 웹 애플리케이션에서 업로드하는 기능을 구현하려면 몇 가지 중요한 도전 과제가 있습니다. 특히 파일의 크기가 수십 기가바이트에서 수백 기가바이트에 이를 때, 효율적이고 안정적인 업로드 방법을 찾아야 합니다. 이 글에서는 대용량 파일 업로드의 기본 개념과 함께 웹 워커를 활용하여 프론트엔드에서 백그라운드로 파일을 업로드하는 방법을 알아보겠습니다.

1. 대용량 파일 업로드의 도전 과제

대용량 파일을 웹 애플리케이션에서 업로드할 때 직면하는 주요 도전 과제는 다음과 같습니다:

  • 네트워크 안정성: 큰 파일은 업로드하는 데 시간이 많이 걸리며, 도중에 네트워크 연결이 불안정해지거나 끊길 가능성이 있습니다.
  • 서버 리소스 관리: 서버는 동시에 여러 사용자가 대용량 파일을 업로드할 수 있도록 준비되어야 하며, 충분한 스토리지와 메모리 관리가 필요합니다.
  • UI 반응성: 대용량 파일 업로드 작업은 클라이언트 측에서 상당한 자원을 소모하므로, UI가 느려지거나 응답하지 않게 될 수 있습니다.

이러한 문제를 해결하기 위해, 파일을 청크로 분할하여 업로드하는 기술과 웹 워커(Web Workers)를 활용한 백그라운드 처리를 결합한 방법을 사용합니다.

2. 파일을 청크로 분할하여 업로드

대용량 파일을 업로드할 때 하나의 큰 파일을 한 번에 전송하는 대신, 파일을 작은 청크(조각)로 나누어 순차적으로 업로드하는 방법을 사용할 수 있습니다. 이렇게 하면 다음과 같은 이점이 있습니다:

  • 네트워크 오류 복구 용이성: 청크 단위로 업로드를 재시도할 수 있어, 전체 업로드 과정이 실패하지 않고 부분적으로 진행 가능합니다.
  • 병렬 업로드 가능: 여러 청크를 동시에 업로드하여 전체 업로드 시간을 단축할 수 있습니다.

3. 웹 워커를 이용한 백그라운드 업로드 처리

웹 워커(Web Workers)는 JavaScript 코드를 메인 스레드와 분리하여 백그라운드에서 실행할 수 있게 해주는 기능입니다. 이를 통해 파일 업로드 작업이 UI의 반응성을 저해하지 않도록 만들 수 있습니다.

웹 워커를 사용한 대용량 파일 업로드 구현의 주요 단계는 다음과 같습니다:

  • 파일 선택 및 준비: 사용자가 파일을 선택하면, 클라이언트 측에서 파일을 청크로 분할합니다.
  • 백그라운드 작업 시작: 웹 워커를 생성하여 파일 업로드 작업을 백그라운드에서 처리합니다.
  • 청크 업로드: 웹 워커는 파일 청크를 순차적으로 또는 병렬로 서버에 업로드하며, 업로드가 성공적으로 완료될 때마다 메인 스레드로 상태 메시지를 전송합니다.
  • 서버에서 청크 병합: 서버는 수신된 청크를 순서대로 병합하여 최종 파일을 완성합니다.

4. 구현 예제

서버 측 코드 (Flask)

서버에서는 클라이언트로부터 청크를 수신하고 이를 병합하여 최종 파일로 저장하는 역할을 합니다.

from flask import Flask, request, jsonify
import os

app = Flask(__name__)

UPLOAD_FOLDER = 'uploads'
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

@app.route('/upload', methods=['POST'])
def upload_chunk():
    file = request.files['file']
    chunk_index = request.form['chunkIndex']
    total_chunks = request.form['totalChunks']
    file_name = request.form['fileName']

    chunk_path = os.path.join(UPLOAD_FOLDER, f'{file_name}_chunk_{chunk_index}')
    file.save(chunk_path)

    if int(chunk_index) + 1 == int(total_chunks):
        merge_chunks(file_name, total_chunks)

    return jsonify({"message": "Chunk uploaded successfully"})

def merge_chunks(file_name, total_chunks):
    with open(os.path.join(UPLOAD_FOLDER, file_name), 'wb') as final_file:
        for i in range(int(total_chunks)):
            chunk_path = os.path.join(UPLOAD_FOLDER, f'{file_name}_chunk_{i}')
            with open(chunk_path, 'rb') as chunk_file:
                final_file.write(chunk_file.read())
            os.remove(chunk_path)

if __name__ == '__main__':
    app.run(debug=True)

클라이언트 측 코드 (HTML & JavaScript)

HTML과 JavaScript로 작성된 클라이언트 측 코드는 파일을 선택하고 웹 워커를 사용해 파일 업로드 작업을 백그라운드에서 수행합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Background File Upload</title>
</head>
<body>
    <input type="file" id="fileInput">
    <button onclick="startUpload()">Start Upload</button>
    <p id="status">Waiting to start...</p>

    <script src="main.js"></script>
</body>
</html>
let worker;

function startUpload() {
    const file = document.getElementById('fileInput').files[0];
    if (file) {
        worker = new Worker('worker.js');
        worker.postMessage({ file });

        worker.onmessage = function(event) {
            document.getElementById('status').textContent = event.data.message;
        };

        worker.onerror = function(error) {
            console.error('Worker error:', error);
            document.getElementById('status').textContent = 'Error occurred during upload.';
        };
    } else {
        alert('Please select a file first.');
    }
}
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB

self.onmessage = async function(event) {
    const file = event.data.file;
    const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
    let chunkIndex = 0;

    while (chunkIndex < totalChunks) {
        const start = chunkIndex * CHUNK_SIZE;
        const end = Math.min(start + CHUNK_SIZE, file.size);
        const chunk = file.slice(start, end);

        const formData = new FormData();
        formData.append('file', chunk);
        formData.append('chunkIndex', chunkIndex);
        formData.append('totalChunks', totalChunks);
        formData.append('fileName', file.name);

        try {
            await fetch('/upload', {
                method: 'POST',
                body: formData,
            });

            self.postMessage({ message: `Uploaded chunk ${chunkIndex + 1} of ${totalChunks}` });
            chunkIndex++;
        } catch (error) {
            self.postMessage({ message: `Error uploading chunk ${chunkIndex + 1}` });
            break;
        }
    }

    if (chunkIndex === totalChunks) {
        self.postMessage({ message: 'File upload complete' });
    }
};

5. 실행 및 결과

  • Flask 서버를 실행하여 클라이언트의 업로드 요청을 받을 준비를 합니다.
  • 브라우저에서 클라이언트 코드를 실행하고 파일을 선택한 뒤 `Start Upload` 버튼을 누르면, 웹 워커가 파일 업로드를 백그라운드에서 처리합니다.
  • 업로드 상태가 실시간으로 UI에 표시되며, 업로드 중에도 사용자는 다른 작업을 계속 수행할 수 있습니다.

결론

대용량 파일 업로드는 웹 애플리케이션에서 중요한 기능 중 하나입니다. 웹 워커를 활용하여 파일 업로드를 백그라운드에서 처리함으로써, 사용자는 업로드 도중에도 UI의 다른 기능을 계속 사용할 수 있습니다. 이 방법은 UI 반응성을 유지하면서도 안정적인 업로드 경험을 제공하는 데 효과적입니다.

답글 남기기