← 경력 기술서

SSE + Worker Thread 기반 엑셀 다운로드 (MVP) 구현 공유

윌로그 재직 중 사내 공유 문서

hub-api에서 차량관리 화면을 기준으로 SSE + worker thread를 이용해 엑셀 다운로드의 MVP 기능을 구현했습니다. 관련 내용을 간단하게 정리하여 문서로 공유드립니다.

기능 구현 고려사항

  1. 해당 글 작성 시점에서 테스트로 사용된 hub-api Stage AWS ECS는 vCPU: 0.5 / memory: 0.5GB 의 스펙으로 컨테이너 3대가 운영중입니다. 엑셀 다운로드 작업은 [조건에 따라 엑셀 생성 → 해당 엑셀 파일 S3 저장 → presigned URL 제공]의 과정을 거치는 CPU-Intensive 작업이기에 Worker Thread를 사용하는 것을 고민했습니다. 참고로 Prod 환경도 글 작성 시점 기준으로 스펙이 동일합니다.

  2. 엑셀 다운로드 요청시, 사용자는 다운로드 진행률을 보고싶어 합니다. 따라서 진행률을 보여줄 수 있는 장치가 필요했습니다. 이를 위해 SSE를 고민했습니다.

  3. 기존 Platform 환경에서는 이러저러한 상황으로 화면에 표시되는 페이지 단위로 엑셀이 다운로드 되었습니다. 이런 부분도 개선이 될 수 있으면 좋을 것 같았습니다.

구현 및 동작

1. 엑셀 다운로드 진행 중

엑셀 다운로드 진행률

2. 요청 대기 상태

대기 상태 1

대기 상태 2

3. 동시 엑셀 생성 요청 모두 마무리 후 메모리 사용량 확인

메모리 사용량 그래프

문제 상황 및 트러블 슈팅

1. 엑셀 생성 후 메모리가 해제되지 않는 문제

vCPU: 0.5 / memory: 0.5GB 환경에서 약 20만 row의 엑셀을 생성시, 메모리 사용량이 약 20~25%가 상승했습니다. 기대하는 상황은 순간적으로 메모리 사용량이 증가해도 생성 완료 후 메모리 해제가 되어야 하는데, 메모리 해제가 되지 않는 상황을 발견했습니다.

⇒ Piscina library를 도입하면서, 자연스럽게 nest.js에서 기본적으로 사용하는 방식 그대로 각 worker thread 내용을 싱글톤으로 만들어서 관리를 하고 있었고, 이를 통해 엑셀이 생성되어도 [기본 점유 메모리 + 엑셀 생성을 통해 추가된 GC 대상 부산물]이 해소되지 않고 적재되면서 메모리 해제가 되지 않았습니다. 싱글톤으로 유지하는 방식을 버리고 요청이 들어올때 Piscina worker thread 로직 객체를 만들어서 엑셀 생성 작업을 진행하고, 해당 객체를 제거하는 방식을 차용했습니다.

2. 클라이언트 연결 해제 후에도 서버 작업이 계속되는 문제

클라이언트에서 엑셀 생성 요청을 통해 생성이 진행되는 도중, 클라이언트에서 연결이 끊어도 서버의 Worker thread는 계속 엑셀을 생성하고 있었습니다. 이런 메모리적 비효율을 해결해야 했습니다.

⇒ Node.js에 내장된 AbortController를 이용해 req의 상태를 감지하도록 했고, 이를 통해 abort 상태가 되면 관련 작업을 중지 및 제거하도록 로직을 만들었습니다.

3. 동시 요청 시 컨테이너 OOM

생성 요청이 동시다발적으로 인입될때, worker thread가 요청마다 작업을 진행하면 순간적으로 메모리 사용량이 폭증하여 해당 컨테이너 자체가 OOM으로 죽어버리는 상황이 있었습니다.

async-mutex 라이브러리를 설치하여 글로벌 세마포어로 요청을 관리하도록 구현했습니다. 현재 서버 스펙을 고려하여 동시 진행할 수 있는 job의 개수는 1개로만 유지합니다. 또한 대기중인 요청은 SSE를 통해 대기 몇번이라는 값을 주도록 로직을 구현했습니다.

4. 대기 중 45초 무응답 시 SSE 커넥션 단절

단순한 구현을 통해 컨테이너 내부에서의 대기열을 구축했습니다. 대기중인 요청이 있더라도 앞선 요청들이 빠르게 처리되면 문제가 없지만, 대기 상태로 SSE response를 45초간 받지 못하면 SSE 통신 에러가 발생하며 커넥션이 끊어졌습니다.

⇒ SSE 통신이 연결된 상태에서 실제 작업을 통해 SSE response가 전달되는 것과 별도로, 20초 interval로 heartbeat라는 별도 타입의 dummy data를 전달하도록 하여 클라이언트가 45초간 아무런 데이터를 전달받지 못하는 상태를 막도록 했습니다.

SSE heartbeat 메시지

운영 및 발전 계획

  1. 현 시점 개발된 내용은 보기에 따라서는 사실 미완의 해결입니다. Server Scale-up / Scale-out을 통해 엑셀 다운로드 관련 사용자 경험을 높일수 밖에 없는 구조이고, 만약 생성 또는 대기 요청을 받은 컨테이너가 죽어버리거나, blue-green 배포를 통해 기존 컨테이너가 swap 되어버리면 생성 요청 건들이 사라지는 구조입니다.

    하지만 MVP 기능이고 위와 같은 상황에서 UI/UX로 재시도를 부탁한다 등으로 어느정도 회피가 가능할 것 같으며, 배포 시점과 맞물릴 상황이 사실상 적을 것이라 판단해 개인적 판단으로는 적어도 26년도 1Q까지는 이 구조로 엑셀 다운로드 기능을 제공할 수 있을 것 같습니다.

  2. AWS MSK 등이 hub-api에 올라오고 관련 리서치가 충분히 더 진행된 다음에 위 엑셀 다운로드 기능에 필요되는 부하를 Message Queue를 통해 해결할 수 있을것이라 생각합니다.