본문으로 건너뛰기
sanguk
Projects로 돌아가기

Project

FreeComics

사내 AI 웹툰 번역 도구를 위한 대용량 ZIP 업로드 서버

사내 AI 웹툰 번역 도구에서 단일 PSD 업로드 한계를 넘어, 여러 PSD를 담은 ZIP을 안정적으로 올리고 번역 워커로 넘기는 서버를 맡았습니다. 청크 재개 업로드, Redis 동시 용량 제한, PM2 다중 프로세스 구조를 중심으로 운영 가능한 업로드 파이프라인을 만들었습니다.

형태
사내 도구 · Backend
주요 기술
ExpressRedisPM2Busboynginx

Outcomes

성과

  • 500Mbps 클라이언트 3대 동시 업로드 기준으로 각 100GB 업로드 성능을 달성했습니다.

Case studies

문제와 근거로 풀어낸 이야기

이력서 불릿을 코드베이스와 맞춰 구체화했습니다. 다이어그램·근거 경로는 각 케이스 아래에 있습니다.

  1. 01

    청크 스트림 업로드와 중단 재개

    실패 시 전체 재전송 없이 chunk_start_idx부터 이어 올리기

    대용량 ZIP을 한 요청으로 받으면 네트워크 끊김·타임아웃 시 처음부터 다시 올려야 했습니다. FreeComics에서는 request_id를 먼저 발급(/translation-re/init)한 뒤, 클라이언트가 파일을 청크로 나눠 POST /translation-re/chunk로 보냅니다.

    각 청크는 Busboy로 multipart 스트림을 받아 /mnt/nvme/uploads/{request_id}/{chunkIndex}.part에 쓰고, Redis 해시 upload_chunks:{request_id}에 인덱스를 pending → done으로 마킹합니다. 헤더 x-request-id, x-chunk-index, x-chunk-total이 이 상태를 맞춥니다.

    상세 조회 API는 Redis에서 첫 pending(없으면 다음 인덱스)을 chunk_start_idx로 돌려주어, 클라이언트는 그 지점부터만 재전송합니다. 전부 done이면 /merge가 worker_threads mergeWorker로 part를 합치고 번역 큐에 넣습니다.

    Mermaid 다이어그램 (포트폴리오용 내보내기)
    • Busboy 스트림 → `{index}.part` 디스크 저장으로 메모리에 전체 파일을 올리지 않음
    • Redis `upload_chunks` pending/done + `chunk_start_idx`로 재개 지점 제공
    • merge는 별도 Worker로 분리해 API 이벤트 루프 블로킹을 줄임
    • src/services/translation_re/translation_re_service.ts — postChunkTranslationFile, getTranslationReFileDetail
    • src/common/workers/workerChunkHandle.ts — markChunk*, isAllChunksReceived, mergeChunksToFile
  2. 02

    Redis 동시 업로드 용량 백프레셔 (300GB)

    진행 중 업로드 합산이 한도를 넘으면 preflight에서 거절

    여러 사용자가 동시에 대용량 ZIP을 올리거나 압축 해제하면 디스크·메모리 압박이 커집니다. 업로드 시작 전 /translation-re/preflight에서 file_size를 받아 ensureUploadCapacity로 허용량을 검사합니다.

    상수 MAX_ING_FILE_SIZE = 300GB이며, Redis 키 uploading:active:*에 저장된 진행 중 용량을 합산합니다. 합산 + 요청 크기가 한도를 넘으면 remaining_bytes·lacking_gb와 함께 400을 돌려 새 업로드를 막습니다.

    허용 시 uploading:active:{request_id}에 file_size를 TTL 600초로 기록하고, 청크 진행·병합 완료 시 키를 갱신·삭제합니다.

    Mermaid 다이어그램 (포트폴리오용 내보내기)
    • `MAX_ING_FILE_SIZE = 300 * 1024^3` 코드 상수
    • preflight API 문서에 동시 업로드 최대 300GB 명시
    • 거절 응답에 remaining/requested/lacking 바이트를 포함해 클라이언트가 대기·재시도 가능
    • translation_re_service.ts — ensureUploadCapacity, MAX_ING_FILE_SIZE
    • controllers/translation_re/preflight/post.ts
  3. 03

    worker_threads vs nginx+PM2 — 페어 조율과 채택

    스크럼에서 대안을 비교하고 프로세스 격리안을 채택

    업로드·압축 해제·번역을 한 Node 프로세스에 두면 OOM·크래시가 전체 API를 멈출 수 있었습니다. 주간 스크럼에서 저는 worker_threads로 같은 프로세스 안 분리를, 동료는 nginx 로드밸런서 + PM2 fork 다중 프로세스를 제안했습니다.

    프로토타이핑으로 스트림 I/O·ZIP·큐 소비 비중과 장애 격리·배포 단순성을 비교한 뒤 동료안을 채택했습니다. ecosystem.config.js는 API 서버 5개(포트 5101~)와 translation-worker 10개를 fork로 띄우고, 프로세스당 --max-old-space-size=8192를 줍니다.

    API는 Redis 큐에 작업을 넣고 워커가 소비해, 업로드 경로와 번역 경로의 프로세스 경계를 나눕니다. nginx는 이 레포 밖 인프라에서 API 포트로 분산하는 형태로 전제합니다.

    Mermaid 다이어그램 (포트폴리오용 내보내기)
    • PM2: API 5 + Worker 10, fork, 힙 8GB
    • 배포: docker-compose Redis + pm2 start ecosystem.config.js
    • 의견 충돌을 프로토타입 근거로 조율한 협업 사례
    • ecosystem.config.js
    • deploy.sh
    • src/workers/translationWorker.ts

Next

FreeComics처럼, 제품 단위로 끝까지 책임집니다

설계·구현·운영까지 한 흐름으로 다루는 풀스택 협업이 필요하면 편하게 문의해 주세요.