Skip to content

[Test] 동시 새 버전 업로드의 단일 Embedding Job 보장 회귀 테스트 추가 - #116

Merged
Gimini-3 merged 2 commits into
developfrom
feature/111
Aug 6, 2026
Merged

[Test] 동시 새 버전 업로드의 단일 Embedding Job 보장 회귀 테스트 추가#116
Gimini-3 merged 2 commits into
developfrom
feature/111

Conversation

@Gimini-3

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

Copy link
Copy Markdown
Contributor

배경

#111은 "같은 Document Version에 살아 있는 Embedding Job이 중복 생성될 수 있다"는 PR #109 리뷰 지적에서 만든 이슈입니다. 구현 전에 전제를 코드로 검증한 결과 현재 구조에서는 발생할 수 없는 상황이었습니다. 따라서 프로덕션 코드는 변경하지 않고, 이 보장을 고정하는 회귀 테스트만 추가합니다.

검증 결과

지적의 전제는 "두 업로드 경로가 DocumentVersion 행 잠금을 얻지 않아 동시에 같은 Version에 Job을 만들 수 있다"였습니다. 실제 코드는 다음과 같습니다.

확인 항목 실제
Job 생성 경로 DocumentUploadService, DocumentVersionUploadService 두 곳
두 경로의 공통점 같은 Transaction에서 새 Version 행을 INSERT하고 그 Version에 Job 1건을 붙임
기존 Version에 Job을 추가하는 경로 없음. 수동 재처리(#108)도 Job을 새로 만들지 않고 기존 Job을 PENDING으로 되돌림
잠금 DocumentVersionUploadService.upload()documentRepository.findByIdForUpdate()로 Document 행을 잠금
기존 DB 제약 uk_document_versions_one_in_progress 부분 Unique Index(V30)가 문서당 진행 중 Version을 1건으로 강제

같은 document_version_id에 살아 있는 Job이 두 건 생기려면 기존 Version에 Job을 만드는 경로가 필요한데 그런 경로가 없습니다. 문서 단위 동시 업로드는 Document 행 잠금과 부분 Unique Index가 이중으로 막습니다.

수동 재처리와 새 버전 업로드가 경쟁하는 경우도 두 경로 모두 Document 행을 잠그므로 직렬화되고, 순서에 따라 DOCUMENT_VERSION_IN_PROGRESS 또는 EMBEDDING_JOB_MANUAL_RETRY_TARGET_INVALID로 갈립니다.

추가한 회귀 테스트

보장은 존재했지만 이 시나리오를 고정하는 테스트가 없었습니다.

같은 문서에 서로 다른 내용의 새 버전을 동시에 2건 업로드
 -> 성공 1건 / DOCUMENT_VERSION_IN_PROGRESS 거부 1건
 -> 진행 중 Version 1건
 -> 살아 있는(PENDING, PROCESSING) Embedding Job 1건

실제 PostgreSQL에서 두 Thread를 Barrier로 동시에 출발시켜 검증합니다. 잠금이나 부분 Unique Index가 제거되면 이 테스트가 먼저 깨집니다.

부분 Unique Index를 추가하지 않은 이유

(document_version_id, embedding_model_id) WHERE status IN ('PENDING','PROCESSING') 부분 Unique Index도 검토했지만 추가하지 않았습니다. 현재 발생할 수 없는 상황을 위해 제약과 오류 계약을 새로 만드는 것이고, 마이그레이션은 적용 후 수정할 수 없기 때문입니다. 앞으로 기존 Version에 Job을 새로 만드는 경로(예: 재인덱싱 API)가 생기면 그 작업에서 함께 판단하는 것이 적절합니다.

테스트

./gradlew test  ->  tests=663 failures=0 errors=0 skipped=0

Closes #111

Summary by CodeRabbit

  • 테스트
    • 문서 버전을 동시에 업로드할 때 한 요청만 성공하고, 다른 요청은 진행 중 상태로 거부되는 동작을 검증하는 통합 테스트를 추가했습니다.
    • 문서 버전과 임베딩 작업의 상태 및 개수를 함께 확인하도록 검증 범위를 강화했습니다.

같은 문서에 새 버전을 동시에 올릴 때 살아 있는 Embedding Job이 하나만
남는다는 보장은 Document 행 잠금과 진행 중 Version 부분 Unique Index가
함께 만들지만, 이 시나리오를 고정하는 테스트가 없었다.

두 요청을 동시에 보내 성공 1건과 DOCUMENT_VERSION_IN_PROGRESS 거부 1건으로
수렴하는지, 진행 중 Version과 살아 있는 Job이 각각 1건인지 실제
PostgreSQL에서 검증한다. 잠금이나 제약이 제거되면 이 테스트가 먼저 깨진다.
@Gimini-3
Gimini-3 requested a review from kangcheolung August 6, 2026 13:39
@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: 53 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: 6fef640f-cf4c-4f46-88f1-b58860e8ee53

📥 Commits

Reviewing files that changed from the base of the PR and between ccf4117 and 8fd95f2.

📒 Files selected for processing (1)
  • src/test/java/com/opensource/docgrid/domain/document/integration/DocumentVersionUploadIntegrationTest.java
📝 Walkthrough

Walkthrough

동일 문서에 대한 두 문서 버전 업로드를 동시에 실행하는 통합 테스트를 추가했습니다. 한 요청의 성공과 다른 요청의 DOCUMENT_VERSION_IN_PROGRESS 실패를 확인합니다. 최종 버전, 진행 중 버전, 활성 Embedding Job 수를 검증합니다.

Changes

문서 버전 동시성 검증

Layer / File(s) Summary
동시 업로드 실행 및 결과 검증
src/test/java/com/opensource/docgrid/domain/document/integration/DocumentVersionUploadIntegrationTest.java
CyclicBarrier, executor, future, timeout을 사용해 두 업로드를 동시에 실행합니다. 하나의 요청만 성공하고 다른 요청은 DOCUMENT_VERSION_IN_PROGRESS로 실패하는지 확인합니다. 최종 문서 버전 수와 진행 중 버전 수, 활성 Embedding Job 수를 조회합니다. UploadOutcome과 SQL 카운트 조회 보조 메서드를 추가합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • DocGrid/backend#22: 문서 업로드 통합 테스트 범위를 동시 DocumentVersion 업로드와 데이터베이스 및 Job 상태까지 확장한 변경입니다.
  • DocGrid/backend#26: 문서 버전 업로드 흐름과 DOCUMENT_VERSION_IN_PROGRESS 동작에 대한 동시성 통합 테스트를 추가한 변경입니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 동시성 테스트는 추가했지만, 이슈 #111이 요구한 DB 수준의 중복 Job 방지 방안과 관련 코드 변경을 구현하지 않았습니다. 부분 Unique Index 또는 모든 Job 생성 경로의 Version 행 잠금 통일 중 하나를 구현하고, 관련 주석과 동시성 테스트를 갱신하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 동시 새 버전 업로드에서 단일 Embedding Job을 보장하는 회귀 테스트 추가라는 변경 내용을 명확하게 설명합니다.
Description check ✅ Passed 변경 배경, 검증 결과, 테스트 범위와 실행 결과를 설명하지만 저장소 템플릿의 일부 제목은 사용하지 않았습니다.
Out of Scope Changes check ✅ Passed 변경은 이슈 #111의 동시성 회귀 테스트 범위에 한정되며, 관련 없는 코드 변경은 확인되지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/111

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

🤖 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/test/java/com/opensource/docgrid/domain/document/integration/DocumentVersionUploadIntegrationTest.java`:
- Around line 196-205: Modify the concurrent upload test around
uploadAfterBarrier and the DocumentVersion creation/locking path so the first
transaction pauses after acquiring the Document row lock or immediately before
creating DocumentVersion, then release it only after the second transaction
reaches the same contention point. Use a test-only latch or hook, preserving the
existing two-task executor and outcome assertions, so the test deterministically
exercises overlapping transactions rather than merely sequential uploads.
🪄 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: 864c0657-0e39-4829-94cd-6ce490198bf6

📥 Commits

Reviewing files that changed from the base of the PR and between 1016b86 and ccf4117.

📒 Files selected for processing (1)
  • src/test/java/com/opensource/docgrid/domain/document/integration/DocumentVersionUploadIntegrationTest.java

기존 Barrier는 Facade 호출 직전만 맞춰서, 한 요청이 upload()를 모두 끝낸 뒤
다른 요청이 실행돼도 성공 1건과 거부 1건이라는 검증을 통과할 수 있었다.
그러면 순차 업로드 거부만 확인하고 Document 행 잠금은 검증하지 못한다.

저장 단계는 쓰기 Transaction 직전이므로 Mock Storage 안에서 두 요청을
붙잡도록 바꿨다. Barrier가 풀렸다는 것은 두 요청이 모두 Commit 전 상태로
저장 단계에 도달했다는 뜻이며, store() 호출 2회를 함께 검증한다.
되돌아간 요청이 후보 Object를 정리하는지도 확인한다.

Document 행 잠금과 진행 중 Version 검사를 제거한 변형 실행에서 이 테스트가
duplicate key 오류로 실패하는 것을 확인해, 보호 장치가 사라지면 실제로
깨진다는 점을 검증했다.
@Gimini-3

Gimini-3 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

리뷰 반영했습니다 (8fd95f2). 지적이 정확했습니다.

기존 Barrier는 Facade 호출 직전만 맞춰서, 한 요청이 upload()를 모두 끝낸 뒤 다른 요청이 실행돼도 "성공 1건 + DOCUMENT_VERSION_IN_PROGRESS 1건" 검증을 통과할 수 있었습니다. 그러면 순차 거부만 확인하고 Document 행 잠금은 검증하지 못합니다.

수정 내용

저장 단계가 쓰기 Transaction 직전이라는 점을 이용해, Mock Storage 안에서 두 요청을 붙잡도록 바꿨습니다. Barrier가 풀렸다는 것은 두 요청이 모두 Commit 전 상태로 저장 단계에 도달했다는 뜻이므로, 이후 둘 다 같은 Document 행을 두고 실제로 경합합니다. store() 호출 2회와 되돌아간 요청의 후보 Object 정리도 함께 검증합니다.

변형 실행으로 비공허성 확인

테스트가 실제로 보호 장치를 검증하는지 확인하려고, findByIdForUpdatefindById로 바꾸고 진행 중 Version 검사를 제거한 상태로 실행해 봤습니다.

MUTATED: tests 4 failures 1
  FAIL: 같은 문서에 새 버전을 동시에 올려도 살아 있는 Embedding Job은 하나만 남는다
        DataIntegrityViolationException: duplicate key value violates ...

보호 장치를 제거하면 이 테스트가 먼저 깨집니다. 동시에 uk_document_versions_one_in_progress 부분 Unique Index가 최후 방어선으로 남아 중복 자체는 막지만, Service 계층 잠금과 검사가 없으면 깔끔한 409 대신 DataIntegrityViolation으로 표면화된다는 것도 함께 확인했습니다. 변형은 실험 후 원복했고 이 PR에 포함되지 않습니다.

수정 후 동일 테스트를 3회 반복 실행해 안정성을 확인했고, 전체 회귀는 663개 통과입니다.

@Gimini-3
Gimini-3 merged commit af39991 into develop Aug 6, 2026
1 check passed
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.

[Fix] 같은 Document Version에 살아 있는 Embedding Job 중복 생성 방지

1 participant