배경
현재 계좌 연동 API는 계좌와 거래내역을 저장한 뒤 소비 분류가 모두 완료될 때까지 기다린 후 응답한다.
소비 분류에 시간이 오래 걸리면서 사용자가 계좌 연동 화면에서 장시간 로딩을 기다려야 한다.
계좌·거래 저장이 끝나면 계좌 연동 응답을 반환하고, 소비 분류는 백그라운드에서 계속 처리하도록 두 작업을 분리한다.
이번 작업에서는 별도의 작업 상태 테이블을 추가하지 않고 기존 거래 및 TXN_ANALYSIS 데이터를 활용한다.
현재 구조
계좌 연동 요청
→ 계좌 및 거래 저장
→ 소비 분류 완료 대기
→ 계좌 연동 API 응답
변경 구조
계좌 연동 요청
→ 계좌 및 거래 저장
→ 트랜잭션 커밋
→ 소비 분류 이벤트 발행
→ 계좌 연동 API 응답
↓
전용 executor에서 소비 분류
↓
TXN_ANALYSIS 저장
목표
- 계좌 연동 API가 소비 분류 완료를 기다리지 않도록 한다.
- 계좌와 거래 저장이 완료되면 사용자 로딩 화면을 종료할 수 있게 한다.
- 소비 분류는 백그라운드에서 계속 수행한다.
- 기존 소비 분류 결과와 fallback 정책을 유지한다.
- 새로운 작업 상태 테이블은 추가하지 않는다.
구현 방향
1. 계좌 연동과 소비 분류 분리
- 계좌와 거래 저장 트랜잭션이 커밋된 후 소비 분류 작업을 시작한다.
- @TransactionalEventListener(phase = AFTER_COMMIT) 등을 활용한다.
- 비동기 리스너는 별도 Spring Bean으로 구성해 self-invocation으로 인해 @async가 무시되지 않도록 한다.
- 계좌 연동 API는 백그라운드 분류 완료를 기다리지 않고 기존 성공 응답을 반환한다.
2. 전용 executor 구성
- 웹 요청 및 기존 FastAPI 배치 executor와 구분되는 전용 executor를 사용한다.
- core/max pool size와 queue capacity를 설정으로 관리한다.
- executor 포화 시 사용자 요청 스레드에서 장시간 분류가 실행되지 않도록 거부 정책을 명확히 정의한다.
- 비동기 작업의 예외가 계좌 연동 성공 응답에 영향을 주지 않게 한다.
3. 중복 실행과 멱등성
- 동일 사용자의 소비 분류 작업이 불필요하게 중복 실행되지 않도록 방어한다.
- classifyUnanalyzedTransactions(userId)는 이미 분석된 거래를 다시 저장하지 않고 미분류 거래만 처리해야 한다.
- 동시에 실행되더라도 중복 TXN_ANALYSIS 저장이나 결과 손상이 발생하지 않도록 검증한다.
- 메모리 기반 중복 방지는 단일 인스턴스에서만 유효하다는 한계를 문서화한다.
4. 분류 상태 판단
새 상태 테이블을 만들지 않고 기존 데이터를 기준으로 상태를 계산한다.
- 분류 대상 거래가 없거나 모든 대상에 분석 결과가 존재: COMPLETED
- 분석되지 않은 대상 거래가 존재: PROCESSING
별도 작업 상태가 없으므로 PENDING과 실제 실행 중인 PROCESSING, FAILED는 정확히 구분할 수 없다. 1차 구현에서는 미분류 거래가 있으면 PROCESSING으로 표현한다.
필요하다면 기존 조회 API를 확장하거나 별도의 읽기 전용 상태 API를 제공한다.
GET /api/accounts/classification-status
응답 예시:
{
"status": "PROCESSING",
"totalCount": 500,
"processedCount": 300,
"remainingCount": 200
}
5. 작업 유실 대응
별도 작업 테이블이 없으므로 서버 재시작 및 배포 시 실행 중인 작업이 유실될 수 있다.
- 미분류 거래가 남아 있으면 안전하게 재실행할 수 있어야 한다.
- 소비 분석 조회 또는 별도의 재분류 요청을 통해 미분류 작업을 다시 시작할 수 있는 복구 경로를 마련한다.
- 자동 복구 스케줄러는 조회 비용과 중복 실행 위험을 검토한 후 필요할 경우 후속 작업으로 분리한다.
6. 로그 및 계측
민감한 거래 원문을 남기지 않고 다음 정보를 기록한다.
[비동기 소비 분류] 작업 시작.
scope=userId=..., remainingCount=...
[비동기 소비 분류] 작업 완료.
scope=userId=..., totalProcessed=..., elapsedMs=...
[비동기 소비 분류] 작업 실패.
scope=userId=..., elapsedMs=..., errorKind=...
거래 설명, 인증정보, API 키 및 전체 traceback은 로그에 남기지 않는다.
테스트
- 계좌 연동 API가 소비 분류 완료를 기다리지 않고 응답하는지 확인
- 거래 저장 트랜잭션 커밋 이후에만 분류가 시작되는지 확인
- 트랜잭션 rollback 시 분류 작업이 시작되지 않는지 확인
- 비동기 분류 실패가 계좌 연동 성공 응답에 영향을 주지 않는지 확인
- 미분류 거래만 처리하는 멱등성 회귀 테스트
- 동일 사용자의 중복 실행 시 데이터 중복 및 손상이 없는지 확인
- 분류 상태별 totalCount, processedCount, remainingCount 계산 확인
- 기존 소비 분류 테스트 통과 확인
완료 조건
- 계좌와 거래 저장 후 소비 분류를 기다리지 않고 계좌 연동 API가 응답한다.
- 소비 분류는 트랜잭션 커밋 이후 전용 executor에서 실행된다.
- 백그라운드 분류 결과가 기존과 동일하게 TXN_ANALYSIS에 저장된다.
- 비동기 작업 실패가 계좌 연동 결과를 실패로 변경하지 않는다.
- 미분류 거래가 남으면 안전하게 다시 분류할 수 있다.
- 기존 소비 분류 및 계좌 연동 테스트가 모두 통과한다.
- 새로운 작업 상태 테이블이나 DB 마이그레이션을 추가하지 않는다.
범위 외
- 정확한 PENDING / PROCESSING / FAILED 작업 상태 관리
- 서버 재시작 후 실행 중이던 작업의 완전한 자동 복구 보장
- Kafka, SQS 등 외부 메시지 큐 도입
- Uvicorn worker 수 조정
- FastAPI LLM chunking 및 timeout 개선
- 프론트엔드의 분류 진행 UI 구현
배경
현재 계좌 연동 API는 계좌와 거래내역을 저장한 뒤 소비 분류가 모두 완료될 때까지 기다린 후 응답한다.
소비 분류에 시간이 오래 걸리면서 사용자가 계좌 연동 화면에서 장시간 로딩을 기다려야 한다.
계좌·거래 저장이 끝나면 계좌 연동 응답을 반환하고, 소비 분류는 백그라운드에서 계속 처리하도록 두 작업을 분리한다.
이번 작업에서는 별도의 작업 상태 테이블을 추가하지 않고 기존 거래 및
TXN_ANALYSIS데이터를 활용한다.현재 구조
변경 구조
계좌 연동 요청
→ 계좌 및 거래 저장
→ 트랜잭션 커밋
→ 소비 분류 이벤트 발행
→ 계좌 연동 API 응답
↓
전용 executor에서 소비 분류
↓
TXN_ANALYSIS 저장
목표
구현 방향
1. 계좌 연동과 소비 분류 분리
2. 전용 executor 구성
3. 중복 실행과 멱등성
4. 분류 상태 판단
새 상태 테이블을 만들지 않고 기존 데이터를 기준으로 상태를 계산한다.
별도 작업 상태가 없으므로 PENDING과 실제 실행 중인 PROCESSING, FAILED는 정확히 구분할 수 없다. 1차 구현에서는 미분류 거래가 있으면 PROCESSING으로 표현한다.
필요하다면 기존 조회 API를 확장하거나 별도의 읽기 전용 상태 API를 제공한다.
GET /api/accounts/classification-status
응답 예시:
{
"status": "PROCESSING",
"totalCount": 500,
"processedCount": 300,
"remainingCount": 200
}
5. 작업 유실 대응
별도 작업 테이블이 없으므로 서버 재시작 및 배포 시 실행 중인 작업이 유실될 수 있다.
6. 로그 및 계측
민감한 거래 원문을 남기지 않고 다음 정보를 기록한다.
[비동기 소비 분류] 작업 시작.
scope=userId=..., remainingCount=...
[비동기 소비 분류] 작업 완료.
scope=userId=..., totalProcessed=..., elapsedMs=...
[비동기 소비 분류] 작업 실패.
scope=userId=..., elapsedMs=..., errorKind=...
거래 설명, 인증정보, API 키 및 전체 traceback은 로그에 남기지 않는다.
테스트
완료 조건
범위 외