StudyDad Loop 제품을 만들며 배운 운영과 설계를 기록합니다.

FamBlend를 중심으로 실제 구현, 운영 메모, GitHub 포트폴리오를 연결해 쌓아가는 StudyDad의 작업 기록입니다.

성장과 기술/시스템 설계

오래 걸리는 엑셀 생성을 동기 API로 처리하면 생기는 문제

박세식 2026. 6. 1. 12:00
반응형

데이터와 리포트 설계 시리즈 2/8

처음 리포트 다운로드 기능을 만들 때 가장 쉬운 방식은 동기 API다.

사용자가 버튼을 누르면 API가 데이터를 조회하고, 엑셀을 만들고, 바로 파일을 응답한다.

작은 데이터라면 이 방식도 괜찮다.

하지만 데이터 범위가 넓어지고 생성 시간이 길어지면 동기 API 방식은 여러 문제를 만든다.

가장 단순한 구조

동기 방식은 이렇게 생겼다.

사용자 클릭
-> POST /api/reports
-> DB 조회
-> 엑셀 생성
-> 파일 응답

구현은 단순하다.

프론트엔드도 요청 하나만 보내면 된다.

서버도 파일을 만들어 바로 응답하면 된다.

하지만 이 구조는 "작업이 짧게 끝난다"는 전제를 가진다.

그 전제가 깨지면 문제가 시작된다.

브라우저 대기 UX

동기 API는 사용자가 요청이 끝날 때까지 기다려야 한다.

리포트 생성이 3초 정도면 괜찮다.

하지만 20초, 40초, 1분 가까이 걸리면 사용자는 불안해진다.

  • 버튼을 다시 누른다.
  • 새로고침한다.
  • 브라우저를 닫는다.
  • 다운로드가 실패했다고 생각한다.

이 행동은 중복 작업을 만들 수 있다.

서버 입장에서는 같은 무거운 리포트를 여러 번 만들게 된다.

API timeout

많은 HTTP 계층에는 timeout이 있다.

API Gateway, load balancer, application server, browser, Lambda 모두 각각 제한이 있을 수 있다.

동기 API에서 작업이 timeout보다 오래 걸리면 사용자는 실패 응답을 받는다.

문제는 서버 작업이 실제로는 계속 진행 중일 수도 있다는 점이다.

사용자는 실패했다고 생각하고 다시 요청한다.

서버는 이미 진행 중인 작업과 새 작업을 동시에 처리할 수 있다.

Lambda timeout과 메모리 문제

Lambda로 엑셀을 생성한다면 timeout과 메모리도 고려해야 한다.

리포트 생성은 다음 자원을 쓴다.

  • DB connection
  • 메모리
  • CPU
  • 임시 디스크
  • 네트워크

데이터가 커지면 엑셀 파일 생성 중 메모리가 커질 수 있다.

함수 timeout을 늘리는 것만으로는 근본 해결이 아닐 수 있다.

처리 모델 자체를 바꿔야 한다.

실패 상태가 남지 않는다

동기 API는 실패하면 보통 요청이 끝난다.

하지만 작업 상태가 별도로 저장되지 않으면 운영자는 무엇이 실패했는지 알기 어렵다.

예를 들어:

  • 요청은 접수되었는가
  • DB 조회 중 실패했는가
  • 파일 생성 중 실패했는가
  • 업로드 중 실패했는가
  • 응답 전송 중 실패했는가

동기 API 하나로 처리하면 이 단계들이 한 요청 로그 안에 묻힌다.

나중에 추적하기 어렵다.

중복 요청 문제

사용자가 같은 조건으로 여러 번 요청할 수 있다.

동기 구조에서는 이를 막기 어렵다.

예를 들어 사용자가 버튼을 두 번 누르면 같은 리포트가 두 번 생성될 수 있다.

이것이 큰 문제가 아닐 수도 있지만, 파일 생성이 무겁거나 DB 부하가 크다면 문제가 된다.

job 단위로 관리하면 같은 조건의 작업이 이미 진행 중인지 확인할 수 있다.

동기 API에서는 이 관리가 자연스럽지 않다.

파일 전달 실패

엑셀 파일을 생성하는 것과 사용자가 다운로드하는 것은 다른 문제다.

동기 응답으로 파일을 바로 내려주면 네트워크 문제로 다운로드가 실패했을 때 재사용할 결과물이 없다.

반면 파일을 object storage에 저장해두면 사용자는 나중에 다시 다운로드할 수 있다.

운영자도 생성 이력을 확인할 수 있다.

리포트 파일은 응답 body로만 존재하기보다 저장 가능한 산출물로 다루는 편이 좋다.

해결 방향: job으로 모델링하기

오래 걸리는 리포트는 HTTP 요청이 아니라 job으로 모델링하는 편이 좋다.

흐름은 이렇게 바뀐다.

POST /api/reports
-> job 생성
-> worker 비동기 실행
-> 즉시 jobId 응답

GET /api/jobs/{jobId}
-> 상태 확인
-> 완료 시 다운로드 URL 반환

사용자는 파일이 아니라 job id를 먼저 받는다.

시스템은 작업 상태를 관리한다.

파일은 완료 후 object storage에 저장된다.

사용자 경험도 더 명확해진다

비동기 방식은 UX가 더 복잡해 보일 수 있다.

하지만 오래 걸리는 작업에서는 오히려 명확하다.

사용자에게 다음 상태를 보여줄 수 있다.

  • 요청 접수됨
  • 처리 중
  • 완료
  • 실패
  • 다시 시도 가능

진행률까지 정확히 제공하지 못해도 상태만 있어도 충분히 낫다.

사용자는 브라우저 요청이 멈춘 것인지, 서버가 처리 중인지 구분할 수 있다.

언제 동기 API도 괜찮은가

동기 API가 항상 나쁜 것은 아니다.

다음 조건이면 괜찮다.

  • 데이터가 작다.
  • 항상 몇 초 안에 끝난다.
  • 파일 생성이 가볍다.
  • 실패해도 다시 요청하면 된다.
  • 생성 이력을 남길 필요가 없다.
  • 사용자 수가 매우 적다.

하지만 시간이 길어지거나 실패 추적이 필요해지는 순간 비동기 구조를 검토해야 한다.

정리

오래 걸리는 엑셀 생성을 동기 API로 처리하면 다음 문제가 생긴다.

  • 브라우저 대기 UX
  • timeout
  • 중복 요청
  • 실패 상태 추적 어려움
  • 파일 재다운로드 어려움
  • DB와 서버 부하

timeout을 늘리는 것은 임시 처방일 수 있다.

근본적으로는 작업을 job으로 모델링해야 한다.

사용자는 요청을 보내고 job id를 받는다.

worker는 백그라운드에서 파일을 만들고, 완료 후 다운로드 URL을 제공한다.

리포트 생성은 응답 시간이 긴 API가 아니라 비동기 작업이다.

함께 볼 GitHub 저장소

반응형
Next Step 이 글은 StudyDad 작업 루프의 한 조각입니다.

글에서 정리한 생각은 GitHub의 코드와 포트폴리오로 이어지고, 일부는 FamBlend 같은 제품 실험으로 확장됩니다.