Skip to content

[Feat] 최종 실패 인덱싱 Job 수동 재처리 지원 추가 구현 - #109

Merged
Gimini-3 merged 9 commits into
developfrom
feature/108
Aug 6, 2026
Merged

[Feat] 최종 실패 인덱싱 Job 수동 재처리 지원 추가 구현#109
Gimini-3 merged 9 commits into
developfrom
feature/108

Conversation

@Gimini-3

@Gimini-3 Gimini-3 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

배경

인덱싱 Job은 실패 유형이 재시도 가능하고 남은 횟수가 있을 때만 PENDING Queue로 재예약됩니다. 재시도 가능 횟수를 모두 소진했거나 재시도 불가 유형으로 종료된 Job은 FAILED로 종결되고, 대상 Version은 FAILED, 그 Version의 Embedding Set은 STALE이 되어 검색에서 제외됩니다.

Claim은 PENDING만, Lease 만료 복구는 PROCESSING만 후보로 삼기 때문에 어떤 자동 경로도 FAILED Job을 되살리지 않습니다. 외부 Embedding 서버 장애처럼 원인이 이미 해소된 뒤에도 같은 문서를 다시 인덱싱하려면 새 Version을 업로드하는 방법밖에 없었습니다.

이 PR은 최종 실패로 종결된 Job만 관리자가 명시적으로 Queue에 되돌리는 경로를 추가합니다.

상태 전이 계약

사전조건:  embedding_jobs.status = FAILED
           document_versions = 해당 문서의 최신 Version, status = FAILED
           documents.deleted_at IS NULL
           같은 Version에 PENDING/PROCESSING Job 없음

전이:      Job:      FAILED  -> PENDING, next_retry_at = NULL
                     locked_by_worker_id, claim_token, locked_at, lock_expires_at, failed_at = NULL
                     retry_count, max_retry_count, error_code, error_message 보존
           Version:  FAILED  -> CHUNKED  (Chunk가 이미 있는 경우, 파싱 생략)
                             -> UPLOADED (Chunk가 없는 경우, 파싱부터 재시작)
           Document: 이전 INDEXED Version이 현재 검색 대상이면 변경 없음
                     그 외에는 INDEXING
           Event:    MANUAL_RETRY (FAILED -> PENDING) 1건 append
           Attempt:  변경 없음

retry_count를 유지하므로 수동 재처리는 추가 실행 1회만 부여합니다. 이번 실행이 다시 실패하면 자동 재시도 없이 즉시 최종 실패로 종결되고, 필요하면 관리자가 다시 요청합니다.

주요 설계 결정

Chunk는 보존하고 대상 Version의 Embedding만 삭제합니다. 최종 실패 시점에 이미 STALE이라 검색에 노출되지 않으며, 남겨두면 CHUNKED Version의 Embedding 수가 0이어야 한다는 기존 불변식에 걸려 재처리 자체가 차단됩니다. 실패한 실행이 남긴 Vector를 다시 ACTIVE로 되살리는 방식은, 완료 검증 단계에서 실패한 경우 그 Set이 실제로 불완전할 수 있어 채택하지 않았습니다.

중복 요청은 멱등 재생이 아닌 명시적 충돌(409)로 처리합니다. 현재 Schema에는 PENDING Job이 자동 재시도 예약인지 수동 재처리 결과인지 구분하는 식별자가 없어, 멱등 재생을 지원하려면 추가 Column이나 Event 조회가 필요합니다.

동시성은 Job 행 잠금 하나로 직렬화합니다. 기존 완료·실패 경로와 같은 Job → Version → Document 순서를 유지합니다. Lease 복구는 PROCESSING + 만료 행만, Claim은 PENDING 행만 후보로 삼으므로 커밋 전에는 경합하지 않고 커밋 후에는 정상 Claim 경로로 흡수됩니다.

API

POST /admin/indexing-jobs/{jobId}/retry     (Body 없음, ADMIN 권한)

200 { jobId, status, documentId, documentVersionId, documentVersionStatus,
      retryCount, maxRetryCount, requeuedAt }

Claim Token, 실패 원인 상세, 내부 예외 정보는 응답에 포함하지 않습니다.

상황 HTTP 코드
Job 없음 404 EMBEDDING-JOB-001
FAILED가 아님 (PENDING·PROCESSING·INDEXED·CANCELED, 중복 요청 포함) 409 EMBEDDING-JOB-008
최신 Version 아님 / 삭제된 문서 / 살아 있는 Job 존재 409 EMBEDDING-JOB-009
Version·Document 종료 데이터 불일치 500 DOCUMENT-INDEXING-004
Job ID가 양수가 아님 400 COMMON-002

테스트

./gradlew test  ->  tests=653 failures=0 errors=0 skipped=0
  • 단위 13개: 재개 지점, 소유권 초기화, 재시도 이력 보존, 상태별 거부
  • Controller 7개: 응답 계약, 민감 정보 미노출, 오류 매핑, ADMIN 권한
  • PostgreSQL 통합 8개: 재처리 후 즉시 Claim 후보 여부, Chunk 유지와 Embedding 정리, 이전 INDEXED Version의 Vector 검색 결과 보존, 동시 요청 2건이 전이 1회 + 충돌 1회로 수렴

Swagger 수동 검증은 수행하지 않았습니다. ADMIN JWT와 자동 재시도를 실제로 소진한 최종 실패 Job이 필요해 로컬 전 구간 실행이 선행돼야 하며, 예정된 로컬 전체 관통 E2E 작업에서 함께 수행하는 것이 적절합니다. 사유와 대체 검증 방법은 테스트 결과 문서에 명시했습니다.

참고

  • Flyway 마이그레이션 없음 (indexing_events.event_type은 CHECK 제약이 없는 VARCHAR(30))
  • SecurityConfig 변경 없음 (/admin/**은 이미 hasRole("ADMIN"))

문서

  • 설계: docs/design/Gimini-3-#108-manual-retry-failed-indexing-job.md
  • 검증 결과: docs/test-results/Gimini-3-#108-manual-retry-failed-indexing-job.md

Closes #108

Summary by CodeRabbit

  • 새 기능

    • 관리자가 최종 실패한 인덱싱 작업을 수동으로 재처리할 수 있습니다.
    • 재처리 결과에 작업 상태, 재시도 정보, 재개 지점 및 예약 시각이 표시됩니다.
    • 기존 청크와 검색 가능한 이전 버전은 보호되며, 관련 감사 기록이 남습니다.
  • 개선 사항

    • 중복 요청, 유효하지 않은 대상, 권한 부족 등에 대해 명확한 오류 응답을 제공합니다.
    • 동시 재처리 요청이 중복 실행되지 않도록 처리됩니다.
  • 테스트

    • 수동 재처리의 상태 전환, 권한, 오류 처리 및 PostgreSQL 통합 시나리오를 검증했습니다.

Claim은 PENDING만, Lease 만료 복구는 PROCESSING만 후보로 삼기 때문에
FAILED로 종결된 Job을 다시 처리할 경로가 없다. 외부 Embedding 서버
장애처럼 원인이 해소된 뒤에도 새 Version을 업로드해야만 재인덱싱할 수
있어, 최종 실패 Job만 Queue로 되돌리는 상태 전이를 추가한다.

EmbeddingJob.requeueForManualRetry()는 FAILED만 PENDING으로 되돌리고
Worker, Claim Token, Lease와 failed_at을 해제해 과거 Token이 후속 단계
저장 권한으로 재사용되지 않게 한다. retry_count와 마지막 오류 Snapshot은
감사 목적으로 유지하므로 수동 재처리는 추가 실행 1회만 부여한다.

DocumentVersion.reopenFailedForRetry()는 파이프라인이 Version 상태로
재개 지점을 판단한다는 기존 계약에 맞춰 UPLOADED 또는 CHUNKED로만
되돌린다. 저장된 Chunk Set과 어긋나는 중간 단계로의 재개를 막기 위한
제한이다.
최종 실패 시점에 대상 Version의 Embedding은 모두 STALE로 전환되어 검색에서
제외되지만 행 자체는 남는다. 이 상태로 재처리하면 CHUNKED Version의
Embedding 수가 0이어야 한다는 기존 불변식에 걸려 임베딩 단계가 시작되지
못한다.

재처리 Transaction이 대상 Version의 Embedding만 한 SQL로 제거하도록
deleteByDocumentVersionId를 추가한다. Chunk Set과 다른 Version의 데이터는
건드리지 않는다.
운영자가 재처리 후 확인해야 하는 값은 Queue 상태와 실제 재개 지점이다.
파싱부터 다시 하는지 임베딩 단계부터 하는지가 소요 시간과 외부 호출량을
결정하므로 documentVersionStatus를 응답에 포함한다.

재처리 직후에는 소유 Worker가 없으므로 Claim 관련 값은 담지 않고,
실패 원인 상세와 내부 오류 정보도 노출하지 않는다.
Job 행을 먼저 잠가 Claim, 완료, 협력적 실패, Lease 복구와의 경쟁을 한
지점에서 직렬화하고 기존 경로와 같은 Job → Version → Document 잠금 순서를
유지한다. 동시 요청은 하나만 상태를 전이하고 나머지는 FAILED가 아닌 상태를
보고 충돌로 끝난다.

재개 지점은 저장된 Chunk 유무로 결정한다. Chunk Set 저장과 CHUNKED 전이가
같은 Transaction에서 일어나므로 Chunk가 존재하면 항상 완전한 Set이고,
파싱을 생략해도 안전하다.

이전 INDEXED Version이 현재 검색 대상이면 문서 상태와 current_version
포인터를 그대로 두어 재처리 중에도 검색 가용성이 끊기지 않게 한다. 그 외의
경우에만 완료 Transaction이 확정할 수 있는 INDEXING으로 되돌린다.

더 새로운 Version이 올라왔거나 같은 Version에 살아 있는 Job이 있으면
재처리해도 완료할 수 없거나 중복 처리가 되므로 거부한다.
POST /admin/indexing-jobs/{jobId}/retry로 최종 실패 Job 한 건을 즉시 Claim
가능한 PENDING으로 되돌린다. /admin/**는 SecurityConfig에서 이미
hasRole("ADMIN")으로 보호되므로 Security 설정은 변경하지 않는다.

재시도 횟수를 초기화하지 않아 이번 재처리가 다시 실패하면 곧바로 최종
실패로 종료된다는 점을 프론트가 알 수 있도록 Operation description에
명시한다.
단위 테스트는 Chunk 유무에 따른 재개 지점, 소유권 초기화와 재시도 이력
보존, Claim Token 없는 감사 Event, 이전 검색 Version 보호를 검증하고
PENDING·PROCESSING·INDEXED·최신 Version 아님·삭제된 문서·살아 있는 Job
존재를 각각 거부하는지 확인한다.

Controller 테스트는 새 Service 주입으로 WebMvcTest Context가 깨지지 않도록
MockitoBean을 추가하고, 응답 필드와 민감 정보 미노출, Job ID 검증, 오류
코드별 HTTP 상태, ADMIN 외 요청 차단을 검증한다.
상태 전이 원자성과 검색 보호는 실제 행 잠금과 Transaction 경계에서만
확인할 수 있어 격리 Schema에 최종 실패 상태를 직접 구성하고 실제 Service를
호출한다.

재처리 후 소유권이 비워진 Job이 곧바로 Claim 후보가 되는지, Chunk는 남고
대상 Version Embedding만 사라지는지, 이전 INDEXED Version의 Vector 검색
결과가 재처리 전후 동일한지, 동시 요청 2건이 전이 1회와 충돌 1회로
수렴하는지를 검증한다.

검증 결과 문서에는 실제 실행한 명령과 653개 통과 결과를 기록하고, Swagger
수동 검증은 수행하지 않았다는 사실과 그 이유 및 대체 검증 방법을 명시한다.
최종 실패 시점의 실제 데이터 상태와 파이프라인 재개 지점 계약을 분석하고,
상태 전이·잠금 순서·API 계약·오류 케이스를 기록한다.

중복 요청을 멱등 재생이 아닌 명시적 충돌로 처리한 이유와, 실패한 실행이
남긴 Vector를 되살리지 않고 삭제하기로 한 이유를 함께 남긴다.
@Gimini-3
Gimini-3 requested a review from kangcheolung August 6, 2026 09:18
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 52 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: CHILL

Plan: Pro Plus

Run ID: 9cb7c2d9-5c96-4fb0-ae42-8aaec92a4a15

📥 Commits

Reviewing files that changed from the base of the PR and between 560a27f and 74d4b09.

📒 Files selected for processing (2)
  • docs/test-results/Gimini-3-#108-manual-retry-failed-indexing-job.md
  • src/test/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryServiceTest.java
📝 Walkthrough

Walkthrough

최종 FAILED 인덱싱 Job을 관리자가 재처리하는 기능이 추가되었습니다. Job은 PENDING으로 복귀하고, 관련 Version과 Embedding 데이터가 재처리 규칙에 따라 갱신됩니다. 관리자 API, 감사 Event, 오류 코드와 단위·Controller·PostgreSQL 통합 검증이 포함됩니다.

Changes

인덱싱 Job 수동 재처리

Layer / File(s) Summary
재처리 계약과 상태 모델
docs/design/..., src/main/java/com/opensource/docgrid/domain/document/entity/DocumentVersion.java, src/main/java/com/opensource/docgrid/domain/embedding/entity/EmbeddingJob.java, src/main/java/com/opensource/docgrid/domain/embedding/dto/response/ManualRetriedIndexingJobResponse.java, src/main/java/com/opensource/docgrid/domain/worker/enums/IndexingEventType.java, src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java
최종 FAILED Job만 재처리 대상으로 허용합니다. Job은 PENDING, Version은 Chunk 존재 여부에 따라 CHUNKED 또는 UPLOADED로 전환합니다. 소유권과 실패 시각은 초기화하고 Retry Count와 Attempt 이력은 유지합니다.
트랜잭션 재처리 오케스트레이션
src/main/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryService.java, src/main/java/com/opensource/docgrid/domain/embedding/repository/EmbeddingRepository.java
Job, Version, Document를 잠근 뒤 재처리 조건을 검증합니다. 대상 Version의 Embedding만 삭제하고 상태 변경과 MANUAL_RETRY Event를 저장합니다.
관리자 API와 응답 변환
src/main/java/com/opensource/docgrid/domain/embedding/controller/IndexingJobAdminController.java, src/main/java/com/opensource/docgrid/domain/embedding/converter/EmbeddingJobConverter.java
POST /admin/indexing-jobs/{jobId}/retry 관리자 API가 추가되었습니다. 성공 결과에는 상태, Version 정보, Retry Count와 Queue 복귀 시각을 반환합니다.
검증과 테스트 결과
src/test/java/com/opensource/docgrid/domain/embedding/controller/IndexingJobAdminControllerTest.java, src/test/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryServiceTest.java, src/test/java/com/opensource/docgrid/domain/embedding/integration/EmbeddingJobManualRetryIntegrationTest.java, docs/test-results/...
상태 전이, 데이터 보존·삭제, 오류 매핑, 권한, 동시 요청, 자동 재시도 경합과 이전 검색 Version 보호를 검증합니다. 검증 문서에는 653개 테스트 통과 결과가 기록되었습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant 관리자
  participant IndexingJobAdminController
  participant EmbeddingJobManualRetryService
  participant EmbeddingRepository
  participant IndexingEvent
  관리자->>IndexingJobAdminController: POST /admin/indexing-jobs/{jobId}/retry
  IndexingJobAdminController->>EmbeddingJobManualRetryService: retry(jobId)
  EmbeddingJobManualRetryService->>EmbeddingRepository: 대상 Version Embedding 삭제
  EmbeddingJobManualRetryService->>IndexingEvent: MANUAL_RETRY Event 저장
  IndexingJobManualRetryService-->>IndexingJobAdminController: 재처리 결과
  IndexingJobAdminController-->>관리자: 200 OK
Loading

Possibly related PRs

  • DocGrid/backend#37: 실패 Version 재처리 중 이전 검색 가능 Version을 보존하는 상태 처리와 연결됩니다.
  • DocGrid/backend#49: EmbeddingJob의 Worker 소유권과 Lease 초기화 동작과 연결됩니다.
  • DocGrid/backend#64: Job Attempt 이력과 Job 잠금 기반 생명주기 처리와 연결됩니다.

Suggested labels: ✨ Feature

Suggested reviewers: kangcheolung

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.59% 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 제목이 최종 실패 인덱싱 Job의 관리자 수동 재처리 기능이라는 주요 변경을 명확하고 간결하게 설명합니다.
Description check ✅ Passed 설명에 배경, 상태 전이, API, 오류, 테스트, 범위와 이슈 종료 정보가 포함되어 있어 요구사항을 대부분 충족합니다.
Linked Issues check ✅ Passed 구현은 #108의 상태 검증, 잠금, 재시도 이력 보존, 검색 Version 보호, 권한, 동시성 및 테스트 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 변경은 수동 재처리 기능과 관련 문서 및 테스트에 한정되며, 제외 범위인 Batch·예약·자동 재시도 변경은 포함하지 않습니다.
✨ 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 feature/108

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

🤖 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 `@docs/design/Gimini-3-`#108-manual-retry-failed-indexing-job.md:
- Line 1: Remove the private sequence label “Gimini-3” from the document title
and rename the file to the {github아이디}-#108-manual-retry-failed-indexing-job.md
convention, preserving the issue number and description.

In `@docs/test-results/Gimini-3-`#108-manual-retry-failed-indexing-job.md:
- Around line 37-38: Update
docs/test-results/Gimini-3-#108-manual-retry-failed-indexing-job.md lines 37-38
to reconcile the total test count with the actual execution results, correcting
the baseline or added counts so the arithmetic is accurate. At lines 42-59,
align the table with the stated 13 passing unit tests by combining the two rows
for retry_clearsOwnershipAndKeepsRetryHistory, or explicitly document that the
table counts rows rather than tests.

In
`@src/main/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryService.java`:
- Around line 186-192: Enforce duplicate Embedding Job prevention at the
database level for the creation paths used by DocumentUploadService.upload() and
DocumentVersionUploadService.upload(): ensure only one row exists per
documentVersion_id and embeddingModel_id with status PENDING or PROCESSING,
using an appropriate unique constraint or equivalent database-enforced
mechanism. Do not rely on the countByDocumentVersionIdAndStatusIn check in
EmbeddingJobManualRetryService as the concurrency guarantee.

In
`@src/test/java/com/opensource/docgrid/domain/embedding/integration/EmbeddingJobManualRetryIntegrationTest.java`:
- Around line 68-72: Update configureDatabase so jwt.secret is supplied through
the test process environment rather than a hardcoded literal, using the existing
environment-variable configuration convention; alternatively, revise the
referenced test-results documentation to accurately describe the current
behavior, keeping code and documentation consistent.

In
`@src/test/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryServiceTest.java`:
- Around line 154-156: Update the Korean comment immediately before the
assertions in EmbeddingJobManualRetryServiceTest to state that the retry history
is preserved and no additional retry is granted, matching retryCount remaining 3
and hasRemainingRetries() being false.
🪄 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: CHILL

Plan: Pro Plus

Run ID: 42d7844b-2690-490f-9e60-0df258f7f748

📥 Commits

Reviewing files that changed from the base of the PR and between e1bd2d2 and 560a27f.

📒 Files selected for processing (14)
  • docs/design/Gimini-3-#108-manual-retry-failed-indexing-job.md
  • docs/test-results/Gimini-3-#108-manual-retry-failed-indexing-job.md
  • src/main/java/com/opensource/docgrid/domain/document/entity/DocumentVersion.java
  • src/main/java/com/opensource/docgrid/domain/embedding/controller/IndexingJobAdminController.java
  • src/main/java/com/opensource/docgrid/domain/embedding/converter/EmbeddingJobConverter.java
  • src/main/java/com/opensource/docgrid/domain/embedding/dto/response/ManualRetriedIndexingJobResponse.java
  • src/main/java/com/opensource/docgrid/domain/embedding/entity/EmbeddingJob.java
  • src/main/java/com/opensource/docgrid/domain/embedding/repository/EmbeddingRepository.java
  • src/main/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryService.java
  • src/main/java/com/opensource/docgrid/domain/worker/enums/IndexingEventType.java
  • src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java
  • src/test/java/com/opensource/docgrid/domain/embedding/controller/IndexingJobAdminControllerTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/integration/EmbeddingJobManualRetryIntegrationTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryServiceTest.java

@@ -0,0 +1,249 @@
# Issue #108 최종 실패 인덱싱 Job 수동 재처리 상세 설계

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

private sequence label을 제거하십시오.

전제: Gimini-33은 private PR sequence label입니다. 파일명과 제목은 실제 이슈 번호만 사용해야 합니다. 파일명을 {github아이디}-#108-manual-retry-failed-indexing-job.md 형식으로 바꾸고 제목에서도 Gimini-3을 제거하십시오.

As per coding guidelines, "Never expose private numbered PR sequence labels" and "docs/design/*.md: PR design documents must use the {github아이디}-#{이슈번호}-{설명}.md naming convention."

🤖 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 `@docs/design/Gimini-3-`#108-manual-retry-failed-indexing-job.md at line 1,
Remove the private sequence label “Gimini-3” from the document title and rename
the file to the {github아이디}-#108-manual-retry-failed-indexing-job.md convention,
preserving the issue number and description.

Source: Coding guidelines

Comment thread docs/test-results/Gimini-3-#108-manual-retry-failed-indexing-job.md Outdated
Comment on lines +186 to +192
// 5. 같은 Version을 처리 중이거나 대기 중인 Job이 있으면 중복 처리가 되므로 거부한다.
if (embeddingJobRepository.countByDocumentVersionIdAndStatusIn(
documentVersion.getId(),
LIVE_JOB_STATUSES
) > 0) {
throw new DocGridException(ErrorCode.EMBEDDING_JOB_MANUAL_RETRY_TARGET_INVALID);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# EmbeddingJob 생성 및 저장 경로를 찾는다.
rg -n -C 8 --type java \
  'EmbeddingJob\.builder\s*\(|new\s+EmbeddingJob\s*\(|embeddingJobRepository\.save\s*\(' \
  src/main/java

# 각 생성 경로가 DocumentVersion 행 잠금을 획득하는지 확인한다.
rg -n -C 8 --type java \
  'findByIdForUpdate\s*\(|DocumentVersionRepository|documentVersionRepository' \
  src/main/java

Repository: DocGrid/backend

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== EmbeddingJobRepository unique constraints/indexes =="
cat -n src/main/java/com/opensource/docgrid/domain/embedding/repository/EmbeddingJRepository.java 2>/dev/null || \
fd -i 'EmbeddingJobRepository.java' . -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}

echo "== EmbeddingJob entity constraints =="
fd -i 'EmbeddingJob.java' . -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}

echo "== Candidate service snippets around DocumentUpload and DocumentVersionUpload saves =="
for f in \
  src/main/java/com/opensource/docgrid/domain/document/service/command/DocumentUploadService.java \
  src/main/java/com/opensource/docgrid/domain/document/service/command/DocumentVersionUploadService.java \
  src/main/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryService.java
do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,230p' "$f" | cat -n
done

echo "== CountBy method =="
rg -n -C 5 'countByDocumentVersionIdAndStatusIn|LIVE_JOB_STATUSES|findTopByDocumentVersionIdAndStatusIn|findFirstByDocumentVersionIdAndStatusIn' src/main/java

Repository: DocGrid/backend

Length of output: 50371


동일 Version의 중복 Embedding Job 생성을 DB에서 강제하십시오.

DocumentUploadService.upload()DocumentVersionUploadService.upload()에서 EmbeddingJob.save()가 호출되지만, 두 경로 모두 DocumentVersion 행 잠금을 얻지 않습니다. countByDocumentVersionIdAndStatusIn은 신규 PENDING Insert와 원자적이지 않아 두 트랜잭션이 동시에 동일한 Version에 Job을 만들 수 있습니다. documentVersion_id, embeddingModel_id, status IN (PENDING, PROCESSING)이 한 번만 존재하도록 Unique 제약 조건을 추가하거나 모든 생성 경로에서 같은 Version 행 잠금을 먼저 획득하도록 변경하십시오. 현재 주석은 count로 “중복 처리가 되므로 거부한다”고 하지만 중복 생성 자체는 막지 못합니다.

🤖 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/opensource/docgrid/domain/embedding/service/command/EmbeddingJobManualRetryService.java`
around lines 186 - 192, Enforce duplicate Embedding Job prevention at the
database level for the creation paths used by DocumentUploadService.upload() and
DocumentVersionUploadService.upload(): ensure only one row exists per
documentVersion_id and embeddingModel_id with status PENDING or PROCESSING,
using an appropriate unique constraint or equivalent database-enforced
mechanism. Do not rely on the countByDocumentVersionIdAndStatusIn check in
EmbeddingJobManualRetryService as the concurrency guarantee.

Source: Coding guidelines

Comment on lines +68 to +72
@DynamicPropertySource
static void configureDatabase(DynamicPropertyRegistry registry) {
registry.add("TEST_DB_SCHEMA", () -> TEST_SCHEMA);
registry.add("jwt.secret", () -> "docgrid-manual-retry-integration-test-secret-key-2026");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

jwt.secret 값을 Test 코드에 직접 넣었습니다.

configureDatabasejwt.secret을 고정 문자열로 등록합니다. 값 자체는 Test 전용이라 실제 위험은 낮습니다. 그러나 docs/test-results/Gimini-3-#108-manual-retry-failed-indexing-job.md 12-13행은 "인증 값은 실행 Process 환경변수로만 주입했다"라고 기술합니다. 코드와 문서가 서로 다릅니다. 환경변수 주입으로 통일하거나 문서 기술을 코드 동작에 맞게 수정해 주세요.

🤖 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/opensource/docgrid/domain/embedding/integration/EmbeddingJobManualRetryIntegrationTest.java`
around lines 68 - 72, Update configureDatabase so jwt.secret is supplied through
the test process environment rather than a hardcoded literal, using the existing
environment-variable configuration convention; alternatively, revise the
referenced test-results documentation to accurately describe the current
behavior, keeping code and documentation consistent.

기준 Test 수 646은 작업 도중 Controller Context가 깨진 실패 실행의 숫자를
그대로 적은 값이었다. develop(e1bd2d2)에서 같은 명령을 실행해 625개
통과를 실측하고 625 + 28 = 653으로 정정한다.

단위 검증 표는 소유권 초기화와 재시도 이력 보존을 두 행으로 나눠 13개
Test와 행 수가 어긋났다. 두 항목이 한 Test에서 함께 검증되므로 한 행으로
합친다.

"추가 실행 1회만 부여한다"는 주석은 바로 아래 hasRemainingRetries()가
false임을 확인하는 어서션과 반대로 읽힌다. 자동 재시도 여유를 남기지
않으므로 재처리 실행이 실패하면 곧바로 최종 실패로 종결된다는 의미가
드러나도록 고친다.
@Gimini-3

Gimini-3 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

리뷰 반영 결과입니다.

반영 (74d4b09)

  • 검증 문서의 기준 Test 수 646 → 625 정정. 646은 작업 도중 Controller Context가 깨진 실패 실행의 숫자를 그대로 적은 값이었고, develop(e1bd2d2)에서 같은 명령을 실행해 625개 통과를 실측했습니다. 625 + 28 = 653으로 맞췄습니다.
  • 단위 검증 표 14행 → 13행. 소유권 초기화와 재시도 이력 보존이 한 Test에서 함께 검증되므로 한 행으로 합쳤습니다.
  • retry_clearsOwnershipAndKeepsRetryHistory의 주석이 어서션과 반대로 읽히는 문제를 수정했습니다.

별도 이슈로 분리 (#111)
같은 Version에 대한 Embedding Job 중복 생성 지적은 타당하지만, 원인이 업로드 경로(DocumentUploadService, DocumentVersionUploadService)에 있어 이 PR의 범위 밖입니다. 부분 Unique Index 추가 또는 생성 경로 잠금 순서 통일 중 선택이 필요하고 Flyway 마이그레이션과 업로드 API 오류 계약 정의가 따라오므로 #111로 분리했습니다.

반영하지 않음
파일명의 Gimini-3을 private sequence label로 보고 제거하라는 지적은 오탐입니다. Gimini-3은 작성자의 GitHub 아이디이고, 파일명은 규칙대로 {github아이디}-#{이슈번호}-{설명}.md 형식(Gimini-3 + #108)을 따르고 있습니다. 기존 문서들도 같은 규칙을 사용합니다.

@Gimini-3
Gimini-3 merged commit 898201b into develop Aug 6, 2026
1 check passed
@Gimini-3 Gimini-3 self-assigned this Aug 6, 2026
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] 최종 실패 인덱싱 Job 수동 재처리 지원 추가 구현

1 participant