Skip to content

[Feat] worker 결과 durable storage 및 멱등 완료 흐름 추가 (#133)#134

Merged
shinae1023 merged 2 commits into
devfrom
feat/#133-worker-presave
Jul 19, 2026
Merged

[Feat] worker 결과 durable storage 및 멱등 완료 흐름 추가 (#133)#134
shinae1023 merged 2 commits into
devfrom
feat/#133-worker-presave

Conversation

@shinae1023

@shinae1023 shinae1023 commented Jul 19, 2026

Copy link
Copy Markdown
Member

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

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

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

변경 내용

  • worker 결과를 메인 DB에 선저장할 수 있도록 worker_task_results 저장소를 추가했습니다.
  • 분석/채용공고 internal worker API에 taskId 기준 결과 저장 및 조회 엔드포인트를 추가했습니다.
  • 기존 complete/finalize 경로에서 결과 스냅샷을 저장하고, 성공 시 DELIVERED 상태로 마킹하도록 연결했습니다.
  • 중복 complete 호출 시에도 taskId 기준으로 안전하게 결과 전달 완료 상태를 유지하도록 보강했습니다.
  • 관련 브리지 테스트를 추가/수정했습니다.

기대 효과

  • OpenAI 호출 성공 후 complete 호출 전에 worker가 죽는 경우에도 결과를 메인 DB에 남길 수 있습니다.
  • 네트워크 타임아웃이나 중복 complete 호출 상황에서 같은 taskId로 재전송하기 쉬워집니다.
  • retry state와 result durability를 분리해서 장애 복구 흐름을 더 명확하게 가져갈 수 있습니다.

📸 작업 화면 스크린샷

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

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

🚨 관련 이슈 번호 [#133 ]

Summary by CodeRabbit

  • New Features

    • Added storage and retrieval of analysis and job-posting worker task results.
    • Worker results now track delivery status, retry attempts, timestamps, and errors.
    • Added APIs for saving and fetching results by task ID.
    • Added clear not-found handling when a stored result is unavailable.
  • Bug Fixes

    • Improved result delivery tracking for completed and failed tasks.
  • Tests

    • Added coverage for result persistence and delivery-status updates.

@shinae1023 shinae1023 self-assigned this Jul 19, 2026
@shinae1023 shinae1023 added the ✨ feat New feature or request label Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 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: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 43a594c4-98d1-4f98-8f40-eccb9047a7a0

📥 Commits

Reviewing files that changed from the base of the PR and between dc73dd0 and 0fe8399.

📒 Files selected for processing (5)
  • .coderabbit.yaml
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeService.java
  • src/main/java/com/jobdri/jobdri_api/domain/workerresult/entity/WorkerTaskResult.java
  • src/main/java/com/jobdri/jobdri_api/domain/workerresult/service/WorkerTaskResultService.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeServiceTest.java
📝 Walkthrough

Walkthrough

Adds persistent worker task results with generated/delivered states, retry metadata, JSON payload storage, and retrieval APIs. Analysis and job posting worker bridges now store results, validate task metadata, track delivery outcomes, and expose internal result endpoints.

Changes

Worker task result lifecycle

Layer / File(s) Summary
Worker result storage model
ops/db/migrations/20260719_worker_task_results.sql, src/main/java/com/jobdri/jobdri_api/domain/workerresult/..., src/main/java/com/jobdri/jobdri_api/global/apiPayload/code/GeneralErrorCode.java
Adds the worker_task_results table, JPA entity, repository, response mapping, JSON serialization, generated/delivered state transitions, retry tracking, and missing-result error code.
Analysis result flow
src/main/java/com/jobdri/jobdri_api/domain/analysis/..., src/test/java/com/jobdri/jobdri_api/domain/analysis/...
Adds validated analysis result storage and retrieval endpoints, persists results during completion, tracks delivery outcomes, and verifies storage and delivered-state behavior.
Job posting result flow
src/main/java/com/jobdri/jobdri_api/domain/jobposting/..., src/test/java/com/jobdri/jobdri_api/domain/jobposting/...
Adds finalize result storage and retrieval endpoints, persists complete/finalize results, tracks delivery outcomes, validates task ownership, and tests the new orchestration.

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

Sequence Diagram(s)

sequenceDiagram
  participant Worker
  participant InternalController
  participant WorkerBridgeService
  participant WorkerTaskResultService
  participant Database
  Worker->>InternalController: POST task result
  InternalController->>WorkerBridgeService: validate and store result
  WorkerBridgeService->>WorkerTaskResultService: upsertGenerated
  WorkerTaskResultService->>Database: serialize and save result
  Worker->>InternalController: GET task result
  InternalController->>WorkerBridgeService: retrieve stored result
  WorkerBridgeService->>WorkerTaskResultService: get(taskId)
  WorkerTaskResultService->>Database: load result
  Database-->>InternalController: WorkerTaskResultResponse
Loading

Possibly related PRs

Suggested labels: ✨ feat

🚥 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
Title check ✅ Passed The title clearly matches the PR’s main change: durable worker result storage plus idempotent completion flow.
Description check ✅ Passed The description follows the template and includes the motivation, detailed changes, effects, and issue reference; only screenshots are left empty.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#133-worker-presave

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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeService.java (1)

114-140: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Reorder validation and address transaction rollback on failure.

There are two critical issues in this flow:

  1. Mutation before validation: workerTaskResultService.upsertGenerated (lines 114-118) is called before verifying if the userId and mockApplyId match the task (lines 120-125). While the subsequent exception currently rolls back the transaction (preventing unauthorized writes), relying on transaction rollback for authorization is fragile. The validation block must be moved before the mutation.
  2. Transaction rollback discards intended state updates: If task.getStatus() == TaskStatus.FAILED, the code explicitly calls markDeliveryFailedIfPresent and then immediately throws a GeneralException. Because this method is annotated with @Transactional, throwing a runtime exception rolls back the entire transaction. As a result, both the upsertGenerated and markDeliveryFailedIfPresent database updates are discarded, defeating the purpose of persisting the failed delivery attempt.

To preserve these state changes while returning an error, you must ensure the result storage operations commit independently (e.g., executing them in a separate service method annotated with @Transactional(propagation = Propagation.REQUIRES_NEW)) before throwing the exception, or return a designated error response object instead.

🐛 Proposed fix for the validation order (Issue `#1`)
     `@Transactional`
     public AnalysisResponse completeTask(String taskId, AnalysisWorkerCompleteRequest request) {
+        AnalysisAsyncTask task = getTask(taskId);
+        if (!task.getUserId().equals(request.userId()) || !task.getMockApplyId().equals(request.mockApplyId())) {
+            throw new GeneralException(
+                    GeneralErrorCode.FORBIDDEN,
+                    "자소서 분석 worker 완료 요청 정보가 작업 정보와 일치하지 않습니다."
+            );
+        }
+
         workerTaskResultService.upsertGenerated(
                 TaskType.ANALYSIS_COMPLETE,
                 taskId,
                 new AnalysisWorkerResultStoreRequest(request.userId(), request.mockApplyId(), request.llmResponse())
         );
-        AnalysisAsyncTask task = getTask(taskId);
-        if (!task.getUserId().equals(request.userId()) || !task.getMockApplyId().equals(request.mockApplyId())) {
-            throw new GeneralException(
-                    GeneralErrorCode.FORBIDDEN,
-                    "자소서 분석 worker 완료 요청 정보가 작업 정보와 일치하지 않습니다."
-            );
-        }
         if (task.getStatus() == TaskStatus.SUCCEEDED) {
🤖 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/service/AnalysisWorkerBridgeService.java`
around lines 114 - 140, Move the task identity validation in the worker
completion flow before calling workerTaskResultService.upsertGenerated, using
the existing getTask lookup and userId/mockApplyId checks. Ensure result-storage
updates in the FAILED branch, including markDeliveryFailedIfPresent and any
required upsertGenerated persistence, execute in an independent REQUIRES_NEW
transaction that commits before the GeneralException is thrown; update the
relevant workerTaskResultService method or service boundary without changing the
existing success behavior.
🧹 Nitpick comments (2)
src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeServiceTest.java (1)

188-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider verifying upsertGenerated as well.

While this test correctly verifies that markDeliveredIfPresent is called, it would be more robust to also assert that the payload was saved via upsertGenerated prior to marking the delivery state.

♻️ Proposed test augmentation
         analysisWorkerBridgeService.completeTask(task.getTaskId(), request);
 
+        verify(workerTaskResultService).upsertGenerated(
+                eq(TaskType.ANALYSIS_COMPLETE),
+                eq(task.getTaskId()),
+                any(AnalysisWorkerResultStoreRequest.class)
+        );
         verify(workerTaskResultService).markDeliveredIfPresent(TaskType.ANALYSIS_COMPLETE, task.getTaskId());
     }
🤖 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/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeServiceTest.java`
around lines 188 - 211, Enhance completeTaskMarksDeliveredForSucceededTask to
also verify that workerTaskResultService.upsertGenerated is invoked with the
expected analysis result payload before markDeliveredIfPresent. Keep the
existing delivery-state verification and use the actual generated-result
arguments produced by completeTask.
ops/db/migrations/20260719_worker_task_results.sql (1)

5-5: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Verify maximum storage capacity for result_payload.

The result_payload column is defined as TEXT. If your database engine is MySQL or MariaDB, the TEXT data type is strictly limited to 64 KB (65,535 bytes). Given that the payload stores JSON containing potentially large LLM responses (AnalysisLlmResponse), the payload size could easily exceed 64 KB, leading to data truncation or insertion errors.

If using MySQL/MariaDB, consider changing this to MEDIUMTEXT (16 MB) or LONGTEXT to safely accommodate large LLM outputs. (If you are using PostgreSQL, TEXT supports up to 1 GB, and this is fine.)

🤖 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 `@ops/db/migrations/20260719_worker_task_results.sql` at line 5, Verify the
database engine used by the migration before finalizing the result_payload
column type. If it targets MySQL or MariaDB, update result_payload from TEXT to
MEDIUMTEXT or LONGTEXT to accommodate large AnalysisLlmResponse JSON payloads;
retain TEXT only when the target engine is PostgreSQL.
🤖 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/workerresult/entity/WorkerTaskResult.java`:
- Around line 35-37: Remove the `@Lob` annotation from the resultPayload field in
WorkerTaskResult and retain its non-nullable `@Column` mapping. Use a plain String
mapping so Hibernate maps result_payload as the existing PostgreSQL TEXT column
rather than LOB/oid semantics.

In
`@src/main/java/com/jobdri/jobdri_api/domain/workerresult/service/WorkerTaskResultService.java`:
- Around line 22-42: Update the transactional annotations on the write methods
upsertGenerated and markDeliveryFailedIfPresent to use Propagation.REQUIRES_NEW,
ensuring their persistence commits independently of any caller transaction.
Leave markDeliveredIfPresent with its existing propagation behavior.

---

Outside diff comments:
In
`@src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeService.java`:
- Around line 114-140: Move the task identity validation in the worker
completion flow before calling workerTaskResultService.upsertGenerated, using
the existing getTask lookup and userId/mockApplyId checks. Ensure result-storage
updates in the FAILED branch, including markDeliveryFailedIfPresent and any
required upsertGenerated persistence, execute in an independent REQUIRES_NEW
transaction that commits before the GeneralException is thrown; update the
relevant workerTaskResultService method or service boundary without changing the
existing success behavior.

---

Nitpick comments:
In `@ops/db/migrations/20260719_worker_task_results.sql`:
- Line 5: Verify the database engine used by the migration before finalizing the
result_payload column type. If it targets MySQL or MariaDB, update
result_payload from TEXT to MEDIUMTEXT or LONGTEXT to accommodate large
AnalysisLlmResponse JSON payloads; retain TEXT only when the target engine is
PostgreSQL.

In
`@src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeServiceTest.java`:
- Around line 188-211: Enhance completeTaskMarksDeliveredForSucceededTask to
also verify that workerTaskResultService.upsertGenerated is invoked with the
expected analysis result payload before markDeliveredIfPresent. Keep the
existing delivery-state verification and use the actual generated-result
arguments produced by completeTask.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a306296c-2cd7-4b8d-979a-bfdefb7f70f7

📥 Commits

Reviewing files that changed from the base of the PR and between 89093cd and dc73dd0.

📒 Files selected for processing (14)
  • ops/db/migrations/20260719_worker_task_results.sql
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/controller/AnalysisWorkerInternalController.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/dto/worker/AnalysisWorkerResultStoreRequest.java
  • src/main/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeService.java
  • src/main/java/com/jobdri/jobdri_api/domain/jobposting/controller/JobPostingWorkerInternalController.java
  • src/main/java/com/jobdri/jobdri_api/domain/jobposting/dto/worker/JobPostingWorkerResultStoreRequest.java
  • src/main/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingWorkerBridgeService.java
  • src/main/java/com/jobdri/jobdri_api/domain/workerresult/dto/WorkerTaskResultResponse.java
  • src/main/java/com/jobdri/jobdri_api/domain/workerresult/entity/WorkerTaskResult.java
  • src/main/java/com/jobdri/jobdri_api/domain/workerresult/repository/WorkerTaskResultRepository.java
  • src/main/java/com/jobdri/jobdri_api/domain/workerresult/service/WorkerTaskResultService.java
  • src/main/java/com/jobdri/jobdri_api/global/apiPayload/code/GeneralErrorCode.java
  • src/test/java/com/jobdri/jobdri_api/domain/analysis/service/AnalysisWorkerBridgeServiceTest.java
  • src/test/java/com/jobdri/jobdri_api/domain/jobposting/service/JobPostingWorkerBridgeServiceTest.java

@shinae1023
shinae1023 merged commit f8b98a7 into dev Jul 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant