최종 융합 프로젝트 마감
프로젝트 관련 PPT
먼저 프로젝트 주제 및 선정 배경

기획의도

차별화 요소

개발환경

협업 구조

내가 맡은 역할

시스템 아키텍처

프로젝트 플로우

내가 개발한 부분에 대한 내용



내가 구현 코드 핵심 내용
1. IntegratedAnalysisScheduler: 원자적 플래그를 이용한 중복 방지
여러 노드에서 배치가 실행될 때 동일한 데이터가 중복 분석되는 것을 방지하기 위해 AtomicBoolean을 활용한 '문지기(Gatekeeper)' 로직을 구현했습니다.
Java
@Component
@RequiredArgsConstructor
public class IntegratedAnalysisScheduler {
private final AtomicBoolean isProcessing = new AtomicBoolean(false);
@Scheduled(fixedDelay = 20000)
public void executeIntegratedAnalysis() {
// [STEP 1] CAS(Compare-And-Set)를 이용한 원자적 실행 보장
if (!isProcessing.compareAndSet(false, true)) {
log.warn("[Batch] 이전 작업이 진행 중입니다. 스킵합니다.");
return;
}
try {
// [STEP 2] 요약(Result)과 채점(Excellent)의 짝이 맞는 데이터만 페이징 조회
List<ResultEventStatus> pendingResults =
resultEventRepository.findReadyToProcessPairs(PageRequest.of(0, 20));
// ... TaskPair 구성 후 Manager에게 위임
} finally {
isProcessing.set(false); // 작업 완료 후 플래그 해제
}
}
}
- 기술 포인트: synchronized 블록보다 성능 오버헤드가 적은 Non-blocking CAS 연산을 사용하여 스케줄러 간의 경합을 제어했습니다.
2. ConsultationAnalysisManager: 비동기 병렬 분석 트랙
상담 하나를 분석할 때 '요약 추출'과 '품질 채점'은 서로 의존성이 없습니다. 이를 CompletableFuture를 이용해 병렬로 실행함으로써 전체 처리 속도를 대폭 개선했습니다.
Java
@Service
public class ConsultationAnalysisManager {
@Transactional(propagation = Propagation.REQUIRES_NEW) // 번들 단위 독립 트랜잭션
public void processIntegratedBundledTasks(List<TaskPair> taskPairs) {
// [STEP 1] AI 비동기 실행 (이중 트랙 병렬화)
CompletableFuture<List<AiExtractionResponse>> extractionFuture = CompletableFuture.supplyAsync(() ->
geminiExtractor.extractBatch(processedRawTexts, groupType, validCodes), geminiTaskExecutor
);
List<CompletableFuture<QualityScoringResponse>> scoringFutures = processedRawTexts.stream()
.map(text -> CompletableFuture.supplyAsync(() ->
geminiQualityScorer.evaluate(text, manualContent), geminiTaskExecutor))
.toList();
// [STEP 2] 결과 수집 및 타임아웃 제어
List<AiExtractionResponse> extractionResults =
extractionFuture.orTimeout(180, TimeUnit.SECONDS).join();
// ... 결과 매칭 및 DB 저장
}
}
- 기술 포인트: REQUIRES_NEW를 통해 각 번들 처리를 격리했습니다. 특정 번들의 AI 분석이 실패하더라도 다른 번들의 성공 결과는 안전하게 커밋됩니다.
3. GeminiExtractor & Scorer: 지수 백오프 기반의 장애 회복력
외부 AI API는 네트워크 상태나 Rate Limit에 민감합니다. 이를 해결하기 위해 지수 백오프(Exponential Backoff) 전략을 적용했습니다.
Java
@Service
public class GeminiExtractor {
@Retryable(
retryFor = {Exception.class},
maxAttempts = 3,
backoff = @Backoff(
delay = 2000, // 초기 2초 대기
multiplier = 2.0, // 2배씩 증가 (2s -> 4s -> 8s)
maxDelay = 10000 // 최대 10초 상한
)
)
public List<AiExtractionResponse> extractBatch(...) {
// API 호출 로직...
}
}
- 기술 포인트: 일시적인 429 Too Many Requests 에러 발생 시 서버에 회복 시간을 주어 성공률을 높였습니다.
- 백오프 공식:
-
$$Wait\ Time = delay \times (multiplier^{attempt-1})$$
4. IntegratedRetryScheduler: 자해 복구(Self-Healing) 시스템
서버가 갑자기 셧다운되어 데이터가 PROCESSING 상태로 영원히 멈추는 '좀비 데이터' 문제를 해결하기 위한 복구 엔진입니다.
Java
@Scheduled(fixedDelay = 600000) // 10분마다 실행
@Transactional
public void retryAndRecoverTasks() {
// [좀비 데이터 구출] 30분 이상 갱신 없는 PROCESSING 태스크 초기화
LocalDateTime threshold = LocalDateTime.now().minusMinutes(30);
resultEventRepository.cleanupStaleProcessingTasks(threshold);
// [실패 데이터 구제] retry_count < 3인 실패 건 자동 재시도
List<ResultEventStatus> failedSummaries =
resultEventRepository.findByStatusAndRetryCountLessThan(EventStatus.FAILED, 3);
failedSummaries.forEach(ResultEventStatus::retry);
}
- 기술 포인트: 에러 핸들링으로 잡을 수 없는 **'인프라적 결함'**까지 고려하여 데이터 생명 주기를 완성했습니다.
🚀 마치며: 백엔드 개발자로서의 성찰
이번 프로젝트를 통해 **'장애는 피하는 것이 아니라 관리하는 것'**임을 배웠습니다. 단순히 API를 연동하는 수준을 넘어, 상태 선점 전략, 비동기 자원 격리, 자해 복구 로직을 고민하며 시스템의 견고함(Robustness)을 한 단계 높일 수 있었습니다.