데이터와 리포트 설계 시리즈 3/8
오래 걸리는 리포트 생성을 비동기로 처리하려면 작업 상태를 저장할 곳이 필요하다.
메모리에 상태를 들고 있으면 안 된다.
서버가 재시작될 수 있고, Lambda는 실행 환경이 유지된다는 보장이 없으며, 운영자는 나중에 작업 이력을 확인해야 한다.
그래서 job table이 필요하다.
job table의 역할
job table은 리포트 생성 작업의 기록장이다.
사용자가 리포트를 요청하면 먼저 job row를 만든다.
worker는 이 job을 처리하면서 상태를 갱신한다.
프론트엔드는 job id로 상태를 조회한다.

job table이 있으면 작업은 추적 가능한 단위가 된다.
기본 상태 모델
처음에는 단순한 상태만으로도 충분하다.
PENDING
IN_PROGRESS
COMPLETE
FAILED
각 상태의 의미는 명확해야 한다.
| 상태 | 의미 |
|---|---|
PENDING |
요청은 접수되었지만 아직 처리 시작 전 |
IN_PROGRESS |
worker가 처리 중 |
COMPLETE |
파일 생성과 저장 완료 |
FAILED |
처리 중 실패 |
상태 이름은 시스템마다 다를 수 있지만, 의미가 모호하면 안 된다.
예를 들어 DONE과 SUCCESS가 섞이면 운영자가 헷갈린다.
job table에 들어갈 컬럼
기본적으로 다음 정보가 필요하다.
id
status
requested_by
request_params
created_at
started_at
completed_at
failed_at
error_message
result_file_url
각 컬럼의 역할은 다르다.
id: 사용자가 상태를 조회할 추적 키status: 현재 작업 상태requested_by: 누가 요청했는지request_params: 어떤 조건으로 생성했는지created_at: 요청 시각started_at: 실제 처리 시작 시각completed_at: 완료 시각error_message: 실패 원인 요약result_file_url: 완료된 파일 위치
처음부터 모든 컬럼이 필요하지 않을 수 있다.
하지만 요청 조건, 상태, 결과 위치, 실패 사유는 가능한 남기는 것이 좋다.
request_params를 남기는 이유
리포트는 조건이 중요하다.
예를 들어 기간, 필터, 대상, 형식에 따라 결과가 달라진다.
따라서 어떤 조건으로 생성했는지 남겨야 한다.
request_params는 JSON 형태로 저장할 수 있다.
{
"from": "2026-05-01",
"to": "2026-05-31",
"type": "monthly",
"format": "xlsx"
}
이 정보가 있어야 나중에 같은 리포트를 재현하거나, 왜 결과가 달랐는지 설명할 수 있다.
실패 사유를 남기는 이유
실패 상태만 남기면 부족하다.
운영자는 왜 실패했는지 알아야 한다.
FAILED 상태와 함께 짧은 오류 메시지를 남긴다.
예시:
DB query timeout
Missing required column
Object storage upload failed
Excel generation memory limit
전체 stack trace를 DB에 모두 넣을 필요는 없다.
상세 로그는 CloudWatch나 서버 로그에 남기고, job table에는 운영자가 볼 수 있는 요약을 남기는 편이 좋다.
상태 전이 규칙
상태는 아무렇게나 바뀌면 안 된다.
가능한 전이를 정한다.
PENDING -> IN_PROGRESS
IN_PROGRESS -> COMPLETE
IN_PROGRESS -> FAILED
FAILED -> PENDING 또는 RETRYING
단순한 시스템에서는 retry 상태 없이 실패 job을 새로 만들 수도 있다.
중요한 것은 상태 전이가 문서화되어 있어야 한다는 점이다.
운영자가 수동으로 상태를 바꿔야 할 때도 어떤 전이가 안전한지 알아야 한다.
멱등성과 중복 요청
사용자가 같은 조건으로 여러 번 리포트를 요청할 수 있다.
이때 정책을 정해야 한다.
선택지는 여러 가지다.
- 매번 새 job을 만든다.
- 같은 조건의
IN_PROGRESSjob이 있으면 기존 job id를 반환한다. - 일정 시간 안에는 완료된 파일을 재사용한다.
- 사용자가 명시적으로 재생성을 요청할 때만 새 job을 만든다.
어떤 방식이든 job table이 있어야 판단할 수 있다.
작업 상태가 저장되어 있어야 "이미 진행 중"인지 알 수 있기 때문이다.
job id는 대화의 기준이다
비동기 작업에서는 job id가 중요하다.
사용자, 프론트엔드, API, worker, 운영자가 같은 작업을 가리키는 공통 언어가 된다.
예를 들어 운영자가 로그를 볼 때도 job id를 기준으로 찾을 수 있다.
가능하면 로그에도 job id를 남긴다.
[jobId=12345] report generation started
[jobId=12345] uploaded result file
[jobId=12345] job complete
이렇게 하면 분산된 로그를 연결하기 쉽다.
정리
비동기 리포트 생성에는 job table이 필요하다.
job table은 단순한 상태 저장소가 아니다.
작업의 추적 단위이고, 운영자의 확인 지점이며, 실패 복구의 기준이다.
처음에는 단순하게 시작해도 된다.
PENDING
IN_PROGRESS
COMPLETE
FAILED
하지만 요청 조건, 결과 파일 위치, 실패 사유, 생성 시각은 가능한 남겨야 한다.
리포트 기능에서 중요한 것은 파일만 만드는 것이 아니다.
작업을 관리할 수 있어야 한다.
함께 볼 GitHub 저장소
'성장과 기술 > 시스템 설계' 카테고리의 다른 글
| 오래 걸리는 엑셀 생성을 동기 API로 처리하면 생기는 문제 (0) | 2026.06.01 |
|---|---|
| 운영 콘솔의 리포트 기능은 왜 생각보다 어려운가 (0) | 2026.05.31 |
| 문서가 개인 브랜딩이 되는 순간 (0) | 2026.05.30 |
| 내부 문서를 블로그 글로 바꾸기 위한 익명화 체크리스트 (0) | 2026.05.29 |
| 배포 가이드는 명령어 모음이 아니다 (0) | 2026.05.28 |
글에서 정리한 생각은 GitHub의 코드와 포트폴리오로 이어지고, 일부는 FamBlend 같은 제품 실험으로 확장됩니다.