Skip to content

[ Refactor ] - 계좌 연동과 소비 분류 작업 비동기 분리 #234

Description

@bigwaveBigwave

배경

현재 계좌 연동 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 구현

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions