-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] 인덱싱 실패 종료 및 지연 재시도 #89
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
6 commits
Select commit
Hold shift + click to select a range
7112ad9
docs: #88 인덱싱 실패 및 지연 재시도 상세 설계 추가
Gimini-3 361d83d
feat: Embedding Job 지연 재시도 Queue 모델 추가
Gimini-3 2bcaf33
feat: 인덱싱 실패 계약과 상태 전이 Guard 추가
Gimini-3 01a0dfc
feat: 인덱싱 실패 처리 API와 Retry 전이 구현
Gimini-3 19b3697
test: 인덱싱 실패 원자성과 동시성 검증
Gimini-3 da7adee
fix: 인덱싱 실패 리뷰 피드백 반영
Gimini-3 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
511 changes: 511 additions & 0 deletions
511
docs/design/Gimini-3-#88-document-indexing-failure-retry.md
Large diffs are not rendered by default.
Oops, something went wrong.
182 changes: 182 additions & 0 deletions
182
docs/test-results/Gimini-3-#88-document-indexing-failure-retry.md
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,182 @@ | ||
| # #88 인덱싱 실패 및 지연 재시도 검증 결과 | ||
|
|
||
| ## 1. 검증 정보 | ||
|
|
||
| - 실행일: 2026-08-03 | ||
| - 실행 환경: macOS Docker Desktop의 일회용 `docgrid-postgres:latest` 컨테이너 | ||
| - 데이터베이스: PostgreSQL 14.6(OpenSQL-PG 호환), 테스트별 격리 Schema, Flyway V1~V35 적용 | ||
| - 애플리케이션: Spring Boot 3.5.16, Java 17 | ||
| - 브랜치: `codex/feature-88-indexing-failure-retry` | ||
|
|
||
| 기존 로컬 DB 컨테이너와 영구 Volume은 변경하지 않았다. 일회용 컨테이너에는 호스트 테스트 접속을 | ||
| 위해 이미지가 생성한 `pg_hba.conf`를 다시 로드했으며, 운영 Secret은 사용하지 않았다. | ||
|
|
||
| ## 2. Swagger/OpenAPI 실제 HTTP 수동 검증 | ||
|
|
||
| - 서버: Test Profile, `http://localhost:18089` | ||
| - Schema: `docgrid_pr89_swagger` | ||
| - 인증: Seed ADMIN 로그인 후 발급한 Bearer Token 사용, Token 값은 기록하지 않음 | ||
| - Swagger 계약: `GET /v3/api-docs`가 `200 OK`이고 실패 Endpoint의 POST Operation이 존재함 | ||
|
|
||
| ### 2.1 최초 실패와 멱등 재생 | ||
|
|
||
| 최초 요청과 같은 요청을 한 번 더 전송했다. | ||
|
|
||
| ```http | ||
| POST /admin/indexing-jobs/8911/attempts/8911/fail | ||
| Authorization: Bearer <admin-token> | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "workerId": 8901, | ||
| "claimToken": "44444444-4444-4444-8444-444444444444", | ||
| "failureType": "STORAGE_UNAVAILABLE", | ||
| "errorMessage": "Storage timeout" | ||
| } | ||
| ``` | ||
|
|
||
| 두 요청 모두 `200 OK`였고 전체 응답 JSON이 같았다. | ||
|
|
||
| ```json | ||
| { | ||
| "success": true, | ||
| "status": 200, | ||
| "data": { | ||
| "jobId": 8911, | ||
| "attemptId": 8911, | ||
| "attemptNo": 1, | ||
| "attemptStatus": "FAILED", | ||
| "failureType": "STORAGE_UNAVAILABLE", | ||
| "failedAt": "2026-08-03T11:01:33.1175", | ||
| "durationMs": 25068 | ||
| }, | ||
| "timestamp": "2026-08-03 11:01:33" | ||
| } | ||
| ``` | ||
|
|
||
| ### 2.2 요청 검증 실패 | ||
|
|
||
| ```http | ||
| POST /admin/indexing-jobs/8901/attempts/8901/fail | ||
| Authorization: Bearer <admin-token> | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "workerId": 8901, | ||
| "claimToken": "11111111-1111-4111-8111-111111111111", | ||
| "failureType": "STORAGE_UNAVAILABLE", | ||
| "errorMessage": "" | ||
| } | ||
| ``` | ||
|
|
||
| 결과: `400 Bad Request`, `COMMON-002`, `errorMessage: 공백일 수 없습니다`. | ||
|
|
||
| ### 2.3 소유권 충돌 | ||
|
|
||
| Job을 소유한 Worker `8901` 대신 `workerId=8902`로 요청했다. | ||
|
|
||
| ```http | ||
| POST /admin/indexing-jobs/8902/attempts/8902/fail | ||
| Authorization: Bearer <admin-token> | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "workerId": 8902, | ||
| "claimToken": "22222222-2222-4222-8222-222222222222", | ||
| "failureType": "STORAGE_UNAVAILABLE", | ||
| "errorMessage": "Storage timeout" | ||
| } | ||
| ``` | ||
|
|
||
| 결과: `409 Conflict`, `EMBEDDING-JOB-003`, | ||
| `현재 Embedding Job 소유권과 요청이 일치하지 않습니다.` | ||
|
|
||
| ### 2.4 상태 충돌 | ||
|
|
||
| `PENDING` Job에 남겨 둔 `STARTED` Attempt로 실패를 요청했다. | ||
|
|
||
| ```http | ||
| POST /admin/indexing-jobs/8903/attempts/8903/fail | ||
| Authorization: Bearer <admin-token> | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "workerId": 8901, | ||
| "claimToken": "33333333-3333-4333-8333-333333333333", | ||
| "failureType": "STORAGE_UNAVAILABLE", | ||
| "errorMessage": "Storage timeout" | ||
| } | ||
| ``` | ||
|
|
||
| 결과: `409 Conflict`, `EMBEDDING-JOB-002`, | ||
| `현재 상태에서는 Embedding Job Attempt를 시작할 수 없습니다.` | ||
|
|
||
| ## 3. 신규 PostgreSQL 통합 검증 | ||
|
|
||
| 실행 명령의 비밀 값은 placeholder로 대체한다. | ||
|
|
||
| ```bash | ||
| DB_HOST=localhost \ | ||
| DB_PORT=55433 \ | ||
| DB_NAME=docgrid \ | ||
| DB_USER=docgrid \ | ||
| DB_PASSWORD='<local-test-password>' \ | ||
| DB_SSLMODE=disable \ | ||
| JWT_SECRET='<64-char-test-secret>' \ | ||
| ./gradlew test \ | ||
| --tests 'com.opensource.docgrid.domain.embedding.integration.DocumentIndexingFailureIntegrationTest' | ||
| ``` | ||
|
|
||
| 결과: `BUILD SUCCESSFUL`, 5개 테스트 통과. | ||
|
|
||
| | 검증 항목 | 결과 | | ||
| | --- | --- | | ||
| | `next_retry_at` 이전 Queue 선택 제외 | 통과 | | ||
| | 정확한 예약 시각의 Queue 선택 허용 | 통과 | | ||
| | 동일 실패 동시 요청의 단일 Retry·동일 응답 수렴 | 통과 | | ||
| | 새 Version 최종 실패 시 이전 INDEXED 검색 Set 보존 | 통과 | | ||
| | 완료와 실패 동시 요청의 단일 상태 전이 | 통과 | | ||
| | RETRY 이벤트 Insert 실패 시 전체 Transaction Rollback | 통과 | | ||
|
|
||
| ## 4. 전체 회귀 검증 | ||
|
|
||
| ```bash | ||
| DB_HOST=localhost \ | ||
| DB_PORT=55433 \ | ||
| DB_NAME=docgrid \ | ||
| DB_USER=docgrid \ | ||
| DB_PASSWORD='<local-test-password>' \ | ||
| DB_SSLMODE=disable \ | ||
| JWT_SECRET='<64-char-test-secret>' \ | ||
| ./gradlew test | ||
| ``` | ||
|
|
||
| 결과: `BUILD SUCCESSFUL`, 514개 테스트 통과, 실패 0, Skip 0. | ||
|
|
||
| 프로젝트 설정에 따라 `benchmark`, `minio-integration`, `claim-concurrency` Tag는 기본 `test`에서 | ||
| 제외됐다. 이번 변경과 직접 관련된 SKIP LOCKED 동시성은 아래 전용 Task로 추가 검증했다. | ||
|
|
||
| ```bash | ||
| DB_HOST=localhost \ | ||
| DB_PORT=55433 \ | ||
| DB_NAME=docgrid \ | ||
| DB_USER=docgrid \ | ||
| DB_PASSWORD='<local-test-password>' \ | ||
| DB_SSLMODE=disable \ | ||
| JWT_SECRET='<64-char-test-secret>' \ | ||
| ./gradlew claimConcurrencyTest | ||
| ``` | ||
|
|
||
| 결과: `BUILD SUCCESSFUL`, 2개 테스트 통과, 실패 0, Skip 0. | ||
|
|
||
| ## 5. 확인된 불변식 | ||
|
|
||
| - Retry 가능 여부는 요청 Boolean이 아니라 `IndexingFailureType` 서버 정책으로 결정된다. | ||
| - `max_retry_count`는 최초 실행 이후 허용할 Retry 횟수로 동작한다. | ||
| - Retry 예약은 Version과 Document 상태를 되돌리지 않고 현재 재개 지점을 보존한다. | ||
| - Retry 예약 시 이전 Worker, Claim Token과 Lease가 제거된다. | ||
| - 동일 실패 요청은 Retry 횟수와 이벤트를 중복 생성하지 않는다. | ||
| - 영구 실패 또는 Retry 소진 시 대상 Version의 ACTIVE Embedding은 STALE이 된다. | ||
| - 이전 INDEXED Version이 있으면 Document 상태와 현재 검색 포인터는 유지된다. | ||
| - 완료와 실패는 Job 행 잠금에서 직렬화되며 한쪽 상태만 커밋된다. | ||
| - 이벤트 저장 실패는 Attempt, Job, Version과 Document 변경을 모두 Rollback한다. |
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
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
44 changes: 44 additions & 0 deletions
44
...java/com/opensource/docgrid/domain/embedding/dto/request/FailDocumentIndexingRequest.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,44 @@ | ||
| package com.opensource.docgrid.domain.embedding.dto.request; | ||
|
|
||
| import com.opensource.docgrid.domain.embedding.enums.IndexingFailureType; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import jakarta.validation.constraints.Pattern; | ||
| import jakarta.validation.constraints.Positive; | ||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| /** | ||
| * 현재 Embedding Job Attempt의 소유권으로 문서 인덱싱 실패를 보고하는 요청 DTO. | ||
| * | ||
| * <p>호출자는 제한된 실패 유형과 진단 메시지만 전달하며, Retry 여부와 다음 실행 시각은 서버 정책이 | ||
| * 결정한다. Claim Token과 오류 메시지는 실패 응답에 다시 노출하지 않는다. | ||
| */ | ||
| public record FailDocumentIndexingRequest( | ||
| @Schema(description = "현재 Job을 소유한 Worker 식별자", example = "7") | ||
| @NotNull | ||
| @Positive | ||
| Long workerId, | ||
|
|
||
| @Schema(description = "현재 Claim의 canonical UUID Token", | ||
| example = "34c19d16-6ae1-4f6a-a35d-0123456789ab") | ||
| @NotBlank | ||
| @Size(max = 36) | ||
| @Pattern( | ||
| regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", | ||
| message = "canonical UUID 형식이어야 합니다." | ||
| ) | ||
| String claimToken, | ||
|
|
||
| @Schema(description = "서버 Retry 정책에 연결되는 인덱싱 실패 유형", | ||
| example = "EMBEDDING_PROVIDER_UNAVAILABLE") | ||
| @NotNull | ||
| IndexingFailureType failureType, | ||
|
|
||
| @Schema(description = "비밀정보와 원문을 제외한 진단 메시지", example = "Embedding provider request timed out") | ||
| @NotBlank | ||
| @Size(max = 2000) | ||
| String errorMessage | ||
| ) { | ||
| } |
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.
Uh oh!
There was an error while loading. Please reload this page.