Skip to content

[feat] 인증 이메일 작업 영속화 및 재시작·다중 서버 재시도 복구 - #271

Merged
PHJ2000 merged 3 commits into
mainfrom
feature/270-durable-email-jobs
Nov 1, 2026
Merged

PHJ2000 merged 3 commits into
mainfrom
feature/270-durable-email-jobs

Conversation

@PHJ2000

@PHJ2000 PHJ2000 commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

✨ Feature PR

🔗 이슈 링크 (필수)

🧩 이 PR의 변경사항 (필수)

전체 중 위치 (Stacked PR 시)

  • 단일 기능 PR. 토큰/작업 저장과 기존 메모리 발송 경로 교체를 함께 적용해야 발송 누락·이중 경로를 방지할 수 있어 테스트 포함 300라인을 초과합니다.
  • develop 브랜치가 없어 최근 기능 PR과 동일하게 main을 기준으로 합니다.

이 PR에서 변경한 것

  • 기존 AFTER_COMMIT + @Async/@retryable은 프로세스 종료 시 작업과 횟수를 잃었습니다. 이제 BEFORE_COMMIT에서 인증 토큰과 암호화된 이메일 작업을 같은 트랜잭션에 저장합니다. 저장 실패 시 토큰도 롤백합니다.

  • Flyway V15: email_jobs와 email_job_attempts. 상태 5종, 시도 횟수, 마지막 시도/다음 재시도/lease 시각과 시도별 결과를 저장합니다.

  • 시작 시와 주기 실행에서 PENDING·기한 도래 RETRY_WAIT·오래된 SENDING을 조회합니다. FOR UPDATE SKIP LOCKED로 선점/횟수 증가/이력 생성을 먼저 커밋하고 SMTP는 트랜잭션 밖에서 호출합니다.

  • 최초 시도 포함 최대 4회, 5→10→20초 백오프. 네트워크/SMTP 4xx는 재시도, 5xx·인증·주소/메시지 오류는 즉시 실패합니다. 코드 없는 MailSendException은 한도 내 재시도합니다.

  • 시도 번호를 완료 UPDATE 조건으로 사용해 오래된 워커가 새 선점 결과를 덮어쓰지 못하게 합니다. 최종 실패와 불명 발송 이력은 DB에 남습니다.

  • 자동 재시도는 토큰을 새로 만들지 않습니다. 기존 3분 수동 재전송 제한을 유지하고, 만료/사용/재전송으로 무효화된 토큰은 선점 시 발송 대상에서 제외합니다.

  • 인증 링크는 AES-GCM으로 암호화하고 종료된 작업에서는 삭제합니다. 복호화 실패만 FAILED/PAYLOAD_DECRYPT_FAILED로 구분하여 암호문을 보존하며 자동 재발송하지 않습니다. 작업 ID는 DB/로그에 남기며, 복구 시도의 새 Trace는 저장된 생산 Trace에 Span Link로 연결합니다.

  • 공용 스케줄러는 기존 emailTaskExecutor에 소비 작업을 전달합니다. 주기 실행은 소비 작업 하나를 요청하며 실제 발송할 작업을 발견했을 때만 최대 3개까지 늘립니다. 실행기에서 실제 시작된 뒤 DB 작업을 선점하므로 유휴 상태의 주기별 빈 조회 3회를 줄입니다. 무효 작업 정리도 20건 배치 한도에 포함하되 다음 후보 조회를 계속합니다.

  • APP_MAIL_ENABLED=false에서는 작업 생성·처리·키 검증을 모두 끄고 기존 Noop 개발 흐름을 유지합니다. 기존 대기 작업을 성공 처리하거나 삭제하지 않습니다. APP_EMAIL_JOBS_POLLING_ENABLED=false만 설정하면 작업 저장은 유지합니다.

  • 미사용 Trace 발송 fixture/RetryConfig와 중복된 비활성화 빈 검증을 제거했습니다. 실제 DB 보존 검증은 유지하고, 유휴 조회·첫 작업 발견 알림·동시 실행 상한을 검증합니다. 새 의존성이나 추가 DB 스키마 변경은 없습니다.

✅ 이 PR이 커버하는 AC (필수)

Covers (이 PR에서 완료)

  • AC1: 상태·시도 횟수·시각 DB 저장.
  • AC2: 시작 및 주기 복구.
  • AC3: 다중 워커 선점과 오래된 워커의 완료 갱신 차단.
  • AC4: DB 기준 최대 4회/지수 백오프/SMTP 오류 분류.
  • AC5: 작업/발송 이력과 exactly-once 한계 문서화 및 재현.
  • AC6: 최종 실패 보존·3분 재전송 제한 유지.
  • AC7: 컨텍스트 재시작·동시 실행·SMTP 성공 후 DB 오류·저장 원자성·재시도 소진 검증.

Not covered (후속 작업)

  • 요청한 AC 중 미구현 항목 없음.
  • 운영 배포, 실제 외부 SMTP 수신함 검증, 강제 프로세스 kill/호스트 장애 실험, 처리량 벤치마크는 실행하지 않았습니다. 재시작 테스트는 동일 PostgreSQL DB에 별도 Spring 컨텍스트를 닫고 새로 열어 ApplicationReadyEvent로 복구를 확인합니다.

🧪 테스트 결과 (필수)

로컬

./gradlew spotlessApply test assemble checkstyleMain checkstyleTest spotlessCheck --no-daemon
python3 scripts/verify-utf8.py
bash scripts/secret-scan.sh
  • ✅ Java 21, PostgreSQL/PostGIS Testcontainers 포함 전체 338개 테스트: failures/errors/skipped 0.
  • ✅ assemble, Checkstyle main/test, Spotless, UTF-8, Gitleaks.
  • 실행기 포화/작업 실패 후 슬롯 복구, 느린 소비 작업의 공용 스케줄러 격리, 메일 비활성화 시 키 없이 기동·기존 작업 보존, 무효 후보 뒤 정상 발송/배치 상한, 복호화 실패 시 암호문 보존을 포함합니다. 장애 주입은 실제 DB 제약조건을 사용합니다.

CI

  • ✅ 최신 커밋 ef294334의 GitHub Actions #41295476127: Backend Build & Test 및 Observability IaC Validation 성공.
  • 최초 CI에서는 변경하지 않은 BeachConditionSchedulerTracingIntegrationTest.skips_recordBoundedReasonsAndContinue가 기대한 span 미관측으로 1회 실패했습니다. 해당 클래스의 로컬 분리 실행과 동일 커밋의 실패 작업 재실행은 통과했습니다. 원인은 확정하지 않았으며 테스트 대기/폴링 추가나 해당 코드 변경은 하지 않았습니다.
  • CodeRabbit 자동 리뷰는 저장소 설정상 비활성화되어 skipped입니다. 사람 리뷰 승인은 아직 없습니다.

Smoke 테스트

  • PostgreSQL에 미완료 작업을 남기고 새로운 컨텍스트를 시작: PENDING/오래된 SENDING 복구.
  • 두 워커 동시 실행: 동일 작업 SMTP 호출 1회.
  • SMTP 수락 뒤 이력 UPDATE에 실제 DB 오류 주입: 성공 UPDATE도 롤백되어 SENDING 유지. lease 만료 후 UNKNOWN 이력과 함께 재발송(호출 2회)을 확인.
  • 최대 4회 및 지연 시각, 영구 오류 즉시 실패, 마지막 시도 불명 시 5회차 금지, 수동 재전송 제한/토큰 무효화, 토큰+작업 롤백 확인.

🧭 영향 범위 체크 (필수)

  • HTTP API 스펙 변경
  • DB 변경: V15 추가, 기존 테이블 파괴 없음
  • 설정 변경: APP_EMAIL_JOBS_PAYLOAD_KEY, polling/lease 설정, SMTP timeout
  • Breaking change: 실제 메일이 활성화된 인스턴스에는 동일한 32바이트 Base64 암호화 키를 배포 전에 설정해야 합니다. 해당 모드에서 미설정/잘못된 길이는 시작을 차단합니다. 메일 비활성화 모드에서는 키가 필요하지 않습니다. 값은 openssl rand -base64 32로 생성해 Secret으로 관리하며, 예시 파일에는 실키를 넣지 않았습니다.

💬 리뷰 포인트 (필수)

  • EmailJobStore: 선점 트랜잭션과 완료 조건/이력 원자성.
  • EmailEventListener/EmailJobService: 토큰 트랜잭션과 작업 저장의 원자성.
  • EmailJobScheduler: 기존 실행기 재사용·3개 제한·제출 거절/실패 후 슬롯 반환.
  • EmailJobWorker/SmtpFailureClassifier: DB 횟수와 오류 분류, SMTP 결과와 DB 기록 오류 분리.
  • README 및 배포 환경 예시: 키 설정·보존, payload 삭제, 이력 접근 제어, 롤백.
  • SMTP 수락 후 DB 커밋 전 종료 또는 lease보다 긴 SMTP/JVM 지연은 중복 발송을 만들 수 있습니다. exactly-once는 보장하지 않습니다. SUCCEEDED는 SMTP 수락이지 받은편지함 도착이 아닙니다.
  • SMTP 연결/읽기/쓰기 timeout은 각 5초, lease 기본 300초(최소 60초)입니다. timeout은 전체 전송 시간의 엄밀한 상한이 아닙니다.
  • 4번째 선점이 불명으로 끝나면 UNKNOWN 이력과 FAILED/ATTEMPTS_EXHAUSTED_UNKNOWN을 보존하며 추가 발송하지 않습니다. 선점 이후 토큰이 무효화되면 이미 시작한 SMTP 전송을 취소할 수 없습니다.
  • 롤백 전 polling을 끄고 진행 중 처리를 종료한 뒤 미완료 작업을 확인해야 합니다. 구버전은 DB 작업을 처리하지 못하고 기존 메모리 작업은 자동 이전되지 않습니다. 구버전 요청을 drain한 뒤 전환해야 합니다.
  • 키 교체 전 미완료 작업 처리 또는 재암호화가 필요합니다. 수신 주소·시도 이력의 자동 보존기간 삭제는 이 PR에 포함하지 않습니다.

📌 공통 체크리스트

  • feature/270-durable-email-jobs 브랜치 및 feat: 내용 커밋 규칙
  • GitHub 이슈 연결
  • 로컬 빌드 및 테스트 통과
  • README·환경변수 예시 업데이트
  • 기존 기능 PR 리뷰어 geonusp에게 리뷰 요청
  • 최신 수정 커밋의 원격 CI 두 작업 성공 확인
  • 사람 리뷰 승인 후 병합 (이 작업에서 병합·배포하지 않음)

- 토큰과 이메일 작업을 원자적으로 저장하고 DB 선점·시도 이력으로 복구
- 최대 4회 지수 백오프와 SMTP 오류 분류, 오래된 선점자의 완료 갱신 차단
- 인증 링크 암호화와 최종 payload 삭제, 중복 발송 한계 및 배포 절차 명시
- 재시작·동시 실행·실제 DB 기록 실패·쿨다운 회귀 테스트 추가

검증: 전체 324 tests, assemble, Checkstyle main/test, Spotless, UTF-8, Gitleaks 통과
Related #270
@PHJ2000
PHJ2000 requested a review from geonusp November 1, 2026 07:44
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3d06481b-78ec-4c99-aac0-bc3bb513749a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

- 기존 emailTaskExecutor에서 최대 3개 소비 작업만 실행하고 실행 후 DB 선점
- 메일 비활성화 시 작업 생성·처리·키 검증을 함께 제외해 로컬 Noop 흐름 유지
- 무효 작업 정리 뒤에도 20건 배치 한도 안에서 다음 후보 조회
- 복호화 실패를 SMTP 오류와 분리하고 복구 판단을 위한 암호문 보존
- 실행기 포화·작업 실패·공용 스케줄러 격리·설정·DB 회귀 테스트 13개 추가

검증: 전체 337 tests, assemble, Checkstyle main/test, Spotless, UTF-8, Gitleaks 통과
Related #270
- 주기마다 소비 작업 하나를 요청하고 실제 작업 발견 시에만 최대 3개로 확장
- 기존 실행기·동시 실행 제한·DB 선점·20건 배치 한도 유지
- 미사용 Trace 발송 fixture와 RetryConfig, 중복 비활성화 설정 검증 제거
- 유휴 조회·작업 발견 알림·슬롯 반환 검증을 현재 실행 흐름에 맞게 조정

검증: 전체 338 tests, assemble, Checkstyle main/test, Spotless, UTF-8, Gitleaks 통과
Related #270
@PHJ2000
PHJ2000 merged commit 35123a7 into main Nov 1, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 인증 이메일 작업 영속화 및 재시작·다중 서버 재시도 복구

1 participant