-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] analysis async를 MQ worker 구조로 전환하고 worker 상태 메타데이터를 확장 (#110) #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e5309fa
[Feat] analysis async를 MQ worker 구조로 전환하고 worker 상태 메타데이터를 확장 (#110)
shinae1023 3b8789b
[Fix] MQ 발행 확인 보강 및 analysis 크레딧 예약 시점 조정 (#110)
shinae1023 b7385b3
[Fix] 채용공고 비동기 task 조회를 사용자 소유 기반으로 제한 (#110)
shinae1023 18b5eb9
[Fix] 채용공고 비동기 retry/timeout 정책 및 MQ 통합 테스트 추가 (#110)
shinae1023 ba9788f
[Fix] analysis async 상태 전이와 worker 메타데이터 처리 보강 (#110)
shinae1023 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
20 changes: 20 additions & 0 deletions
20
ops/db/migrations/20260704_analysis_async_tasks_worker_metadata.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS failure_reason VARCHAR(40); | ||
|
|
||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS worker_id VARCHAR(100); | ||
|
|
||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS retry_count INTEGER NOT NULL DEFAULT 0; | ||
|
|
||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS max_retry_count INTEGER NOT NULL DEFAULT 3; | ||
|
|
||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP; | ||
|
|
||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS last_attempt_at TIMESTAMP; | ||
|
|
||
| ALTER TABLE analysis_async_tasks | ||
| ADD COLUMN IF NOT EXISTS queue_latency_millis BIGINT; |
5 changes: 5 additions & 0 deletions
5
ops/db/migrations/20260704_job_posting_async_tasks_user_id.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS user_id BIGINT; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_job_posting_async_tasks_user_id_created_at | ||
| ON job_posting_async_tasks (user_id, created_at DESC); |
20 changes: 20 additions & 0 deletions
20
ops/db/migrations/20260704_job_posting_async_tasks_worker_metadata.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS failure_reason VARCHAR(40); | ||
|
|
||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS worker_id VARCHAR(100); | ||
|
|
||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS retry_count INTEGER NOT NULL DEFAULT 0; | ||
|
|
||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS max_retry_count INTEGER NOT NULL DEFAULT 3; | ||
|
|
||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP; | ||
|
|
||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS last_attempt_at TIMESTAMP; | ||
|
|
||
| ALTER TABLE job_posting_async_tasks | ||
| ADD COLUMN IF NOT EXISTS queue_latency_millis BIGINT; |
119 changes: 119 additions & 0 deletions
119
...va/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisWorkerInternalController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.controller; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncStatusResponse; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisResponse; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.worker.AnalysisWorkerCompleteRequest; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.worker.AnalysisWorkerContextRequest; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.worker.AnalysisWorkerContextResponse; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.worker.AnalysisWorkerFailureRequest; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.worker.AnalysisWorkerRetryRequest; | ||
| import com.jobdri.jobdri_api.domain.analysis.dto.worker.AnalysisWorkerRunningRequest; | ||
| import com.jobdri.jobdri_api.domain.analysis.service.AnalysisAsyncTaskService; | ||
| import com.jobdri.jobdri_api.domain.analysis.service.AnalysisWorkerBridgeService; | ||
| import com.jobdri.jobdri_api.global.apiPayload.ApiResponse; | ||
| import com.jobdri.jobdri_api.global.security.InternalApiKeyValidator; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestHeader; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/internal/worker/analysis") | ||
| public class AnalysisWorkerInternalController { | ||
|
|
||
| private static final String INTERNAL_API_KEY_HEADER = "X-Internal-Api-Key"; | ||
|
|
||
| private final InternalApiKeyValidator internalApiKeyValidator; | ||
| private final AnalysisWorkerBridgeService analysisWorkerBridgeService; | ||
| private final AnalysisAsyncTaskService analysisAsyncTaskService; | ||
|
|
||
| @PostMapping("/tasks/{taskId}/running") | ||
| public ApiResponse<Void> markRunning( | ||
| @RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey, | ||
| @PathVariable String taskId, | ||
| @Valid @RequestBody AnalysisWorkerRunningRequest request | ||
| ) { | ||
| internalApiKeyValidator.validate(internalApiKey); | ||
| analysisWorkerBridgeService.markRunning(taskId, request.workerId(), request.retryCount(), request.submittedAt()); | ||
| return ApiResponse.onSuccess("자소서 분석 worker 작업 시작 상태를 반영했습니다."); | ||
| } | ||
|
|
||
| @PostMapping("/tasks/{taskId}/retry") | ||
| public ApiResponse<Void> markRetry( | ||
| @RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey, | ||
| @PathVariable String taskId, | ||
| @Valid @RequestBody AnalysisWorkerRetryRequest request | ||
| ) { | ||
| internalApiKeyValidator.validate(internalApiKey); | ||
| analysisWorkerBridgeService.markRetry( | ||
| taskId, | ||
| request.failureReason(), | ||
| request.errorMessage(), | ||
| request.retryCount(), | ||
| request.workerId(), | ||
| request.queueLatencyMillis() | ||
| ); | ||
| return ApiResponse.onSuccess("자소서 분석 worker 작업 재시도 상태를 반영했습니다."); | ||
| } | ||
|
|
||
| @PostMapping("/tasks/{taskId}/failed") | ||
| public ApiResponse<Void> failTask( | ||
| @RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey, | ||
| @PathVariable String taskId, | ||
| @Valid @RequestBody AnalysisWorkerFailureRequest request | ||
| ) { | ||
| internalApiKeyValidator.validate(internalApiKey); | ||
| analysisWorkerBridgeService.failTask( | ||
| taskId, | ||
| request.failureReason(), | ||
| request.errorMessage(), | ||
| request.retryCount(), | ||
| request.workerId(), | ||
| request.queueLatencyMillis() | ||
| ); | ||
| return ApiResponse.onSuccess("자소서 분석 worker 작업 실패 상태를 반영했습니다."); | ||
| } | ||
|
|
||
| @PostMapping("/context") | ||
| public ApiResponse<AnalysisWorkerContextResponse> getContext( | ||
| @RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey, | ||
| @Valid @RequestBody AnalysisWorkerContextRequest request | ||
| ) { | ||
| internalApiKeyValidator.validate(internalApiKey); | ||
| return ApiResponse.onSuccess( | ||
| "자소서 분석 worker 컨텍스트 조회에 성공했습니다.", | ||
| analysisWorkerBridgeService.getContext(request.taskId(), request.userId(), request.mockApplyId()) | ||
| ); | ||
| } | ||
|
|
||
| @PostMapping("/tasks/{taskId}/complete") | ||
| public ApiResponse<AnalysisResponse> completeTask( | ||
| @RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey, | ||
| @PathVariable String taskId, | ||
| @Valid @RequestBody AnalysisWorkerCompleteRequest request | ||
| ) { | ||
| internalApiKeyValidator.validate(internalApiKey); | ||
| return ApiResponse.onSuccess( | ||
| "자소서 분석 worker 작업 완료 상태를 반영했습니다.", | ||
| analysisWorkerBridgeService.completeTask(taskId, request) | ||
| ); | ||
| } | ||
|
|
||
| @GetMapping("/tasks/{taskId}") | ||
| public ApiResponse<AnalysisAsyncStatusResponse> getTask( | ||
| @RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey, | ||
| @PathVariable String taskId | ||
| ) { | ||
| internalApiKeyValidator.validate(internalApiKey); | ||
| return ApiResponse.onSuccess( | ||
| "자소서 분석 worker 작업 상태 조회에 성공했습니다.", | ||
| analysisAsyncTaskService.getTaskStatusByTaskId(taskId) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisTaskMessage.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import lombok.Builder; | ||
|
|
||
| import java.time.Instant; | ||
| import java.util.UUID; | ||
|
|
||
| @Builder | ||
| public record AnalysisTaskMessage( | ||
| String messageId, | ||
| String taskType, | ||
| String taskId, | ||
| Long userId, | ||
| Long mockApplyId, | ||
| int retryCount, | ||
| int maxRetryCount, | ||
| Instant submittedAt | ||
| ) { | ||
|
|
||
| public static AnalysisTaskMessage of( | ||
| String taskId, | ||
| Long userId, | ||
| Long mockApplyId, | ||
| int maxRetryCount | ||
| ) { | ||
| return AnalysisTaskMessage.builder() | ||
| .messageId(UUID.randomUUID().toString()) | ||
| .taskType("ANALYSIS") | ||
| .taskId(taskId) | ||
| .userId(userId) | ||
| .mockApplyId(mockApplyId) | ||
| .retryCount(0) | ||
| .maxRetryCount(Math.max(0, maxRetryCount)) | ||
| .submittedAt(Instant.now()) | ||
| .build(); | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
.../java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerCompleteRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.analysis.dto.llm.AnalysisLlmResponse; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record AnalysisWorkerCompleteRequest( | ||
| @NotNull Long userId, | ||
| @NotNull Long mockApplyId, | ||
| @Valid @NotNull AnalysisLlmResponse llmResponse, | ||
| @NotBlank String workerId, | ||
| Long queueLatencyMillis | ||
| ) { | ||
| } |
11 changes: 11 additions & 0 deletions
11
...n/java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerContextRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record AnalysisWorkerContextRequest( | ||
| @NotBlank String taskId, | ||
| @NotNull Long userId, | ||
| @NotNull Long mockApplyId | ||
| ) { | ||
| } |
25 changes: 25 additions & 0 deletions
25
.../java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerContextResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record AnalysisWorkerContextResponse( | ||
| Long userId, | ||
| Long mockApplyId, | ||
| String companyName, | ||
| String jobTitle, | ||
| String task, | ||
| String requirements, | ||
| String preferredQualifications, | ||
| String bigClassificationName, | ||
| String middleClassificationName, | ||
| String detailClassificationName, | ||
| List<AnalysisWorkerQuestionItem> questions | ||
| ) { | ||
| public record AnalysisWorkerQuestionItem( | ||
| Long questionId, | ||
| String question, | ||
| String answer, | ||
| int charLimit | ||
| ) { | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
...n/java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerFailureRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.analysis.entity.AnalysisAsyncTask.FailureReason; | ||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record AnalysisWorkerFailureRequest( | ||
| @NotBlank String errorMessage, | ||
| @NotNull FailureReason failureReason, | ||
| @Min(0) int retryCount, | ||
| @NotBlank String workerId, | ||
| Long queueLatencyMillis | ||
| ) { | ||
| } | ||
15 changes: 15 additions & 0 deletions
15
...ain/java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerRetryRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.analysis.entity.AnalysisAsyncTask.FailureReason; | ||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record AnalysisWorkerRetryRequest( | ||
| @NotBlank String errorMessage, | ||
| @NotNull FailureReason failureReason, | ||
| @Min(0) int retryCount, | ||
| @NotBlank String workerId, | ||
| Long queueLatencyMillis | ||
| ) { | ||
| } |
13 changes: 13 additions & 0 deletions
13
...n/java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerRunningRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.jobdri.jobdri_api.domain.analysis.dto.worker; | ||
|
|
||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| public record AnalysisWorkerRunningRequest( | ||
| @NotBlank String workerId, | ||
| @Min(0) int retryCount, | ||
| Instant submittedAt | ||
| ) { | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: JobDri-Developer/BackEnd
Length of output: 1171
Pass the telemetry fields through the failure path.
workerId,queueLatencyMillis, andopenAiRequestIdare validated onAnalysisWorkerFailureRequestbut dropped byfailTask(...), so failure records never get those observability fields. Thread them intofailTask/markFailedso failures can be attributed.🤖 Prompt for AI Agents