공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 16 / 22

긴 작업 비동기 처리와 진행률

202 Accepted, 작업 상태, 폴링·SSE, 취소, 중복 방지, 완전 종료
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 즉시 응답 패턴: 202 Accepted

작업을 제출하면 서버는 작업을 큐에 올리기만 하고, 202 Accepted 와 함께 작업 ID·상태 조회 URL 을 바로 돌려줍니다. 실제 처리는 이후에 백그라운드 스레드가 합니다.

200 OK 대신 202 를 쓰는 이유는 의미가 다르기 때문입니다. 200 은 "요청한 일을 다 끝냈다"는 뜻이고, 202 는 "접수는 했지만 아직 끝나지 않았다"는 뜻입니다. 클라이언트는 이 차이로 폴링이 필요하다는 걸 알 수 있습니다.

2.2 상태 모델과 진행률

작업은 다섯 가지 상태 중 하나를 가집니다.

상태 의미
PENDING 큐에 있고 아직 시작 전
RUNNING 처리 중
DONE 정상 완료
FAILED 처리 중 예외 발생
CANCELLED 사용자가 취소함

진행률은 보통 processed / total 건수와 사람이 읽을 메시지("3/100건 처리 중") 두 가지를 함께 보여줍니다. 건수만 있으면 화면에 막대바는 그릴 수 있어도, 지금 뭘 하는 중인지는 알기 어렵습니다.

2.3 폴링 vs SSE vs WebSocket

세 방식 모두 "지금 상태가 어떤지" 클라이언트에 전달하는 수단이지만 성격이 다릅니다.

방식 방향 적합한 경우
폴링 클라이언트가 요청 구현 단순, 진행률 갱신 빈도 낮아도 됨
SSE 서버가 단방향 push 진행률 실시간 표시, 서버→클라이언트만 필요
WebSocket 양방향 채팅처럼 클라이언트도 계속 보내야 함

사내 관리자 화면 정도라면 폴링만으로 충분한 경우가 많습니다. 몇 초에 한 번 상태를 물어봐도 사용자 체감에는 큰 차이가 없습니다.

진행률 바를 부드럽게 보여줘야 하거나 폴링 트래픽이 부담될 정도로 사용자가 많다면 SSE 가 낫습니다. WebSocket 은 이 용도에는 과합니다. 클라이언트가 서버로 보낼 말이 없는데 양방향 연결을 유지할 이유가 없습니다.

2.4 nginx 뒤에서 SSE 가 안 올 때

SSE 는 오래 열려 있는 HTTP 연결인데, nginx 는 기본적으로 응답을 버퍼에 모았다가 한꺼번에 보냅니다. 이 상태로 두면 브라우저에 이벤트가 뚝뚝 끊겨 도착하거나 아예 안 옵니다.

text
location /jobs/ {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_read_timeout 3600s;
    add_header X-Accel-Buffering no;
}

proxy_buffering off 는 nginx 가 응답을 즉시 클라이언트로 흘려보내게 합니다. X-Accel-Buffering: no 는 서버 응답 헤더로도 같은 효과를 줄 수 있어, 이 레슨의 Main 도 이 헤더를 직접 붙입니다. proxy_read_timeout 은 기본값(보통 60초)보다 길게 잡아야 연결이 중간에 끊기지 않습니다.

2.5 작업 저장소: 메모리와 그 한계

이 레슨의 JobManager 는 작업을 ConcurrentHashMap 에 담습니다. 서버 인스턴스가 하나뿐이고 재기동이 드물다면 이것으로 충분합니다.

인스턴스가 여러 대라면 문제가 생깁니다. A 서버가 접수한 작업을 B 서버가 조회하면 "그런 작업 없음"이 됩니다. 이때는 작업 상태를 DB 의 JOB 테이블에 저장하고, 결과 파일은 공유 스토리지나 각 서버가 접근 가능한 경로에 둡니다.

서버가 재기동되면 메모리에 있던 RUNNING 작업 정보는 사라집니다. DB 로 관리한다면, 기동 시점에 RUNNING 으로 남아 있는 행을 FAILED 로 일괄 정리하는 배치를 넣어야 합니다. 그러지 않으면 사용자 화면에 영원히 "처리 중"인 작업이 남습니다.

2.6 중복 제출 방지

사용자가 버튼을 두 번 누르거나, nginx 504 뒤 브라우저가 재시도하면 같은 작업이 중복 실행될 수 있습니다. 가장 간단한 방어는 "같은 사용자가 실행 중인 작업이 있으면 새 제출을 409 Conflict 로 거절"하는 것입니다.

더 엄격하게 하려면 클라이언트가 요청마다 고유한 멱등키(idempotency key)를 헤더로 보내고, 서버가 같은 키로 이미 처리한 요청이면 새로 실행하지 않고 기존 결과를 그대로 돌려주는 방식을 씁니다. 결제·정산처럼 중복 실행 피해가 큰 작업에 적합합니다.

2.7 협력적 취소

자바 스레드는 강제로 죽일 안전한 방법이 없습니다. Thread.stop() 은 오래전에 사용이 금지됐습니다. 그래서 취소는 "그만해 달라고 부탁하고, 작업 스스로 확인해서 멈추는" 협력적 방식으로 합니다.

작업 루프 안에서 매 단계마다 취소 플래그를 확인하고, 켜져 있으면 하던 일을 정리하고 빠져나옵니다. 이미 일부 쓴 결과 파일이 있다면 삭제해서 반쪽짜리 결과가 남지 않게 합니다. Thread.interrupt() 를 함께 걸어두면 Thread.sleep 이나 블로킹 I/O 중에도 더 빨리 깨어날 수 있습니다.

2.8 스레드 풀 크기와 서버 완전 종료

작업 처리용 스레드 풀은 요청 처리용 풀과 분리합니다. 크기를 무제한으로 두면 동시 제출이 몰릴 때 스레드가 폭증해 서버 전체가 느려집니다. 고정 크기 풀 + 큐 한도를 두고, 큐까지 가득 차면 새 제출을 거절하는 편이 안전합니다.

서버를 내릴 때는 진행 중인 작업을 갑자기 끊지 않고 완전 종료해야 합니다. ExecutorService.shutdown() 으로 새 작업 접수를 막고, awaitTermination 으로 진행 중인 작업이 끝나거나 정해진 시간이 지날 때까지 기다린 뒤, 그래도 안 끝나면 shutdownNow() 로 인터럽트를 겁니다.

2.9 Spring 대응표

JDK 이 레슨 Spring Boot
ExecutorService 수동 생성 @EnableAsync + ThreadPoolTaskExecutor
작업 메서드 직접 큐잉 메서드에 @Async
SSE 수동 스트리밍 SseEmitter 반환
풀 크기 설정 하드코딩 spring.task.execution.pool.*

@Async 는 같은 클래스 안에서 this.메서드() 로 호출하면 프록시를 안 거쳐서 동작하지 않는 self-invocation 함정이 있습니다. 반드시 다른 빈을 통해 호출해야 합니다. spring.task.execution.pool.core-size, max-size, queue-capacity 로 풀과 큐 크기를 설정합니다.

핵심 원리
  • 2.1 즉시 응답 패턴: 202 Accepted
  • 2.2 상태 모델과 진행률
  • 2.3 폴링 vs SSE vs WebSocket
  • 2.4 nginx 뒤에서 SSE 가 안 올 때
  • 2.5 작업 저장소: 메모리와 그 한계
  • 2.6 중복 제출 방지
  • 2.7 협력적 취소
  • 2.8 스레드 풀 크기와 서버 완전 종료
  • 2.9 Spring 대응표
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제