배경 및 목표
채용담당자가 인적성 검사 결과를 조회할 때, 외부 서비스에서 실시간으로 파일을 가져와 처리하는 구조였습니다. 최초 조회 시 파일 다운로드 + 파싱에 6초가 걸렸고, 그 동안 DB 커넥션을 점유하여 다른 요청에도 영향을 주었습니다.
graph LR subgraph before["❌ 기존: 실시간 폴링"] C["채용담당자"] -->|"결과 조회"| API["API"] API -->|"외부 파일 다운로드"| EXT["외부 서비스"] EXT -->|"파일 처리 (6초)"| API end API -->|"커넥션 6초 점유"| DB[("DB")]
목표
- 최초 조회 시 6초 지연·커넥션 점유를 없애고 즉시 응답한다.
- 롱 트랜잭션 없이 결과지를 사전 적재한다.
해결 방법과 해결 후보군
후보군 비교
| 방식 | 설명 | 한계 |
|---|---|---|
| 실시간 폴링 | 조회 시점에 외부 처리 | 6초 지연 + 커넥션 점유 |
| 조회 결과 캐싱 | 첫 조회 후 캐시 | 첫 조회는 여전히 6초 |
| 사전 배치 적재 (채택) | 미리 가져와 DB 적재 | 조회 즉시 응답, 롱 트랜잭션 제거 |
1. 사전 배치 처리 방식으로 전환
조회 시점에 외부 서비스를 호출하는 대신, 주기적 배치로 결과를 미리 가져와 DB에 적재하는 방식으로 변경했습니다.
fun preloadResults() {
val pendingIds = resultRepository.findPendingIds()
pendingIds.forEach { id ->
val result = externalClient.fetchResult(id)
if (result != null) {
resultRepository.save(result.toEntity())
}
}
}2. 조회 시 즉시 응답
채용담당자가 결과를 조회하면, 이미 적재된 데이터를 반환하므로 외부 호출이 불필요하고 DB 커넥션 점유 문제도 해소되었습니다.
graph LR subgraph before["❌ 기존"] Q1["조회 요청"] --> E1["외부 파일 다운로드"] --> P1["파싱 (6초)"] --> R1["응답"] end subgraph after["✅ 개선"] B["배치: 미리 적재"] --> DB[("DB")] Q2["조회 요청"] --> DB --> R2["즉시 응답"] end
결과
| 지표 | 기존 | 개선 |
|---|---|---|
| 결과 조회 | 6초 | 1초 이하 (83% 단축) |
| 커넥션 점유 | 길음 | 해소 |
| 롱 트랜잭션 | 발생 | 제거 |
모니터링
- 결과지 조회 응답시간(6초→1초 이하)과 커넥션 점유 시간을 관측한다.
- 사전 배치 처리 성공률·지연을 관측한다.