Skip to content

Analysis async sweep/credit 안정성 개선 (#271) - #272

Merged
shinae1023 merged 7 commits into
devfrom
refactor/#271-analysis-organize
Aug 12, 2026
Merged

Analysis async sweep/credit 안정성 개선 (#271)#272
shinae1023 merged 7 commits into
devfrom
refactor/#271-analysis-organize

Conversation

@shinae1023

@shinae1023 shinae1023 commented Aug 12, 2026

Copy link
Copy Markdown
Member

✨ 어떤 이유로 PR를 하셨나요?

  • feature 병합
  • 버그 수정(아래에 issue #를 남겨주세요)
  • 코드 개선
  • 코드 수정
  • 배포
  • 기타(아래에 자세한 내용 기입해주세요)

📋 세부 내용 - 왜 해당 PR이 필요한지 작업 내용을 자세하게 설명해주세요

analysis async sweep와 credit 상태 전이 쪽에서 남아 있던 구조적 결합과 멱등성 취약 지점을 정리한 PR입니다.
기존에는 sweep가 전체 PENDING/RUNNING task 엔티티를 읽고 있었고, credit refund 책임이 여러 클래스에 흩어져 있었으며, release 후 재예약 시 같은 reference를 재사용할 수 있어 크레딧 차감/환불 흐름이 불안정할 여지가 있었습니다.

이번 PR에서는 아래 4개 커밋을 포함합니다.

  • [Refactor] async timeout 조회를 batch query로 분리 (#271)
  • [Refactor] async sweep 시간 의존성을 Clock으로 치환 (#271)
  • [Refactor] async credit refund 책임 공용화 (#271)
  • [Fix] async credit 상태 전이 멱등성 보강 (#271)

주요 변경 사항은 아래와 같습니다.

  • timeout 대상 taskId만 조회하는 pending/running 전용 batch query 추가
  • sweep coordinator가 batch 단위로 timeout 대상 id를 순회하고, 각 task만 잠금 조회 후 실패 처리하도록 변경
  • Clock bean 및 coordinator 주입 추가로 sweep 기준 시간 테스트 가능화
  • AnalysisAsyncCreditCoordinator를 도입해 refund 책임을 공용 협력자로 통합
  • worker bridge / sweep / task service의 중복 refund 로직 제거
  • reserve / confirm / release 상태 전이를 coordinator 중심으로 정리
  • credit_reference_version 필드 및 versioned async reference 규칙 추가
  • credit_reference_id unique index 추가
  • release 후 재예약 시 새 reference를 사용하도록 변경해 중복 차감/환불을 방지
  • 관련 helper/worker/sweep 회귀 테스트 보강

📸 작업 화면 스크린샷

  • 없음

⚠️ PR하기 전에 확인해주세요

  • 로컬테스트를 진행하셨나요?
  • 머지할 브랜치를 확인하셨나요?
  • 관련 label을 선택하셨나요?

🚨 관련 이슈 번호 [ #271 ]

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@shinae1023, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a4d94eea-da32-44d3-9484-b636c363d7b7

📥 Commits

Reviewing files that changed from the base of the PR and between 8e5574a and 9f14879.

📒 Files selected for processing (15)
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/application/usecase/async/AnalysisAsyncUseCase.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/AnalysisAsyncTask.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinator.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncWorkerBridge.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncCreditCoordinator.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncSweepService.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncTaskService.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisWorkerBridgeService.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/core/AnalysisCreditService.java
  • src/main/resources/schema.sql
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinatorTest.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncCreditCoordinatorTest.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncFacadeServiceTest.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncTaskServiceTest.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisWorkerBridgeServiceTest.java
📝 Walkthrough

Walkthrough

비동기 분석 흐름이 PUBLISH_FAILED 작업을 재개해 재처리하도록 변경되었습니다. 만료 작업은 주입된 Clock과 상태별 페이지 조회를 사용해 스윕합니다. 관련 저장소 메서드와 서비스, 유스케이스, 테스트가 추가되었습니다.

Changes

비동기 분석 작업 수명 주기

Layer / File(s) Summary
발행 실패 작업 복구
src/main/java/com/jobdri/jobdri_api/domain/analysis/{repository,entity,service,application}/..., src/test/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncFacadeServiceTest.java
최신 PUBLISH_FAILED 작업을 조회하고, 대상 작업만 PENDING으로 재개합니다. 재개된 작업은 기존 발행 처리 흐름을 사용합니다. 재발행 실패 시 동일 작업을 다시 실패 처리하는 테스트를 추가했습니다.
만료 작업 배치 스윕
src/main/java/com/jobdri/jobdri_api/domain/analysis/{infrastructure,repository,service}/async/..., src/main/java/com/jobdri/jobdri_api/global/config/ClockConfig.java, src/test/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinatorTest.java
PENDINGRUNNING 만료 작업 ID를 페이지 단위로 조회합니다. 주입된 Clock으로 만료 시각을 계산하고, 작업별 트랜잭션과 예외 처리를 적용합니다. 배치 처리와 빈 배치 동작을 검증합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • JobDri-Developer/BackEnd#120: AnalysisAsyncTaskRepositoryAnalysisAsyncSweepService의 만료 스윕 구현에 직접 연결됩니다.
  • JobDri-Developer/BackEnd#126: 작업 실패 사유, 재시도, 워커 메타데이터 및 스윕 수명 주기의 기반 변경과 연결됩니다.
  • JobDri-Developer/BackEnd#114: AnalysisAsyncTaskService와 비동기 작업 생성 및 수명 주기 처리에 직접 연결됩니다.

Suggested labels: ♻️ refactor, 🐛 fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 제목은 async sweep과 credit 안정성 개선이라는 주요 변경 사항을 명확하게 요약합니다.
Description check ✅ Passed 변경 이유, 세부 내용, 테스트 여부, 대상 브랜치, 스크린샷, 관련 이슈를 대부분 작성했습니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#271-analysis-organize

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

Comment @coderabbitai help to get the list of available commands.

@shinae1023
shinae1023 marked this pull request as ready for review August 12, 2026 01:39
@shinae1023 shinae1023 self-assigned this Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinator.java`:
- Around line 80-103: The sweepTimedOutTaskIds loop can repeatedly load the same
failed first batch because sweepTimedOutTask leaves failed tasks eligible for
selection. Update the coordinator to use keyset pagination or track and exclude
task IDs already processed during the current sweep, ensuring progress and
termination; add a test in AnalysisAsyncTaskSweepCoordinatorTest covering all
100 IDs failing and verifying the sweep does not repeat the batch and either
terminates or advances to the next batch.

In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncTaskService.java`:
- Around line 199-209: Update reopenPublishFailureTask to make the
recoverable-state validation and reopenForRepublish transition atomic by using a
PESSIMISTIC_WRITE-locked task lookup or an equivalent conditional update. Ensure
only the request that successfully performs the transition calls
publishAfterCommit and triggers processing; concurrent retries must not
republish the same task. Add a concurrency test covering simultaneous
resubmission attempts.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c2c487e8-81a6-4c9a-af7e-81105cc149dc

📥 Commits

Reviewing files that changed from the base of the PR and between edac47b and 8e5574a.

📒 Files selected for processing (9)
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/application/usecase/async/AnalysisAsyncUseCase.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/entity/AnalysisAsyncTask.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinator.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/repository/AnalysisAsyncTaskRepository.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncSweepService.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncTaskService.java
  • src/main/java/com/jobdri/jobdri_api/global/config/ClockConfig.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinatorTest.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/async/AnalysisAsyncFacadeServiceTest.java

Comment on lines +80 to +103
private int sweepTimedOutTaskIds(TaskIdBatchLoader taskIdBatchLoader) {
int expiredCount = 0;
for (AnalysisAsyncTask task : analysisAsyncTaskRepository.findByStatusIn(EnumSet.of(AnalysisAsyncTaskStatus.PENDING, AnalysisAsyncTaskStatus.RUNNING))) {
try {
expiredCount += transactionTemplate.execute(status -> sweepTimedOutTask(task.getTaskId()));
} catch (RuntimeException e) {
log.error("Analysis async task sweep failed for taskId={}", task.getTaskId(), e);
while (true) {
List<String> taskIds = taskIdBatchLoader.load();
if (taskIds.isEmpty()) {
return expiredCount;
}
for (String taskId : taskIds) {
expiredCount += sweepTimedOutTask(taskId);
}
if (taskIds.size() < SWEEP_BATCH_SIZE) {
return expiredCount;
}
}
return expiredCount;
}

private int sweepTimedOutTask(String taskId) {
try {
return transactionTemplate.execute(status -> sweepTimedOutTaskInTransaction(taskId));
} catch (RuntimeException e) {
log.error("Analysis async task sweep failed for taskId={}", taskId, e);
return 0;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

실패한 첫 batch가 무한 반복될 수 있습니다.

처리 실패 시 task는 PENDING 또는 RUNNING 상태로 남습니다. 다음 반복도 PageRequest.of(0, SWEEP_BATCH_SIZE)를 사용하므로 같은 첫 100개 ID를 다시 조회합니다. 이 상태가 지속되면 sweep 스레드가 종료하지 않습니다.

  • src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinator.java#L80-L103: 실패한 ID를 한 sweep에서 다시 처리하지 않도록 keyset pagination 또는 처리 완료 ID 추적을 적용하세요.
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinatorTest.java#L94-L97: 100개 ID가 모두 실패하는 경우 sweep가 같은 batch를 반복하지 않고 종료하거나 다음 batch로 진행하는 테스트를 추가하세요.

As per path instructions, 비동기 처리 안정성 및 실패 복구 검증 우선 지침을 적용했습니다.

📍 Affects 2 files
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinator.java#L80-L103 (this comment)
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinatorTest.java#L94-L97
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/infrastructure/async/AnalysisAsyncTaskSweepCoordinator.java`
around lines 80 - 103, The sweepTimedOutTaskIds loop can repeatedly load the
same failed first batch because sweepTimedOutTask leaves failed tasks eligible
for selection. Update the coordinator to use keyset pagination or track and
exclude task IDs already processed during the current sweep, ensuring progress
and termination; add a test in AnalysisAsyncTaskSweepCoordinatorTest covering
all 100 IDs failing and verifying the sweep does not repeat the batch and either
terminates or advances to the next batch.

Source: Path instructions

@shinae1023 shinae1023 changed the title Analysis async sweep timeout handling 개선 (#271) Analysis async sweep/credit 안정성 개선 (#271) Aug 12, 2026
@shinae1023
shinae1023 merged commit 86c30e6 into dev Aug 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant