-
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
Changes from 1 commit
e5309fa
3b8789b
b7385b3
18b5eb9
ba9788f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| 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()); | ||
| 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()); | ||
| 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) | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| 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, | ||
| String creditReferenceId, | ||
| int retryCount, | ||
| int maxRetryCount, | ||
| Instant submittedAt | ||
| ) { | ||
|
|
||
| public static AnalysisTaskMessage of( | ||
| String taskId, | ||
| Long userId, | ||
| Long mockApplyId, | ||
| String creditReferenceId, | ||
| int maxRetryCount | ||
| ) { | ||
| return AnalysisTaskMessage.builder() | ||
| .messageId(UUID.randomUUID().toString()) | ||
| .taskType("ANALYSIS") | ||
| .taskId(taskId) | ||
| .userId(userId) | ||
| .mockApplyId(mockApplyId) | ||
| .creditReferenceId(creditReferenceId) | ||
| .retryCount(0) | ||
| .maxRetryCount(Math.max(0, maxRetryCount)) | ||
| .submittedAt(Instant.now()) | ||
| .build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| 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, | ||
| String openAiRequestId | ||
| ) { | ||
| } | ||
| 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 | ||
| ) { | ||
| } |
| 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 | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| 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, | ||
| String openAiRequestId | ||
| ) { | ||
|
Comment on lines
+8
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check whether the bridge/task service's failTask/markFailed persist worker metadata anywhere else
rg -n -A10 'void failTask' src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeService.java
rg -n -A10 'markFailed' src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisAsyncTaskService.javaRepository: JobDri-Developer/BackEnd Length of output: 1171 Pass the telemetry fields through the failure path. 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| 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, | ||
| String openAiRequestId | ||
| ) { | ||
| } |
| 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 | ||
| ) { | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.