1. 목적
여러 Worker가 동시에 PENDING Embedding Job을 Claim해도 Job 소유권이 정확히 한 번만 배정되고, 대량 Queue가 중복과 누락 없이 완전히 소진되는지 실제 OpenSQL에서 검증한다.
이 이슈는 Claim TPS나 P95를 측정하는 성능 테스트가 아니라 동시성 정합성 테스트다. 처리량과 Lock 경합 비교는 별도의 성능 테스트에서 다룬다.
2. 검증 대상 기능
다음 Job Claim 동작을 검증 대상으로 사용한다.
SELECT ... FOR UPDATE SKIP LOCKED
- PENDING → PROCESSING 상태 전환
locked_by_worker_id
- UUID
claim_token
locked_at, lock_expires_at
- 동일 Transaction의
LOCKED 이벤트 저장
기존 2-Worker/1-Job 통합 테스트는 최소 회귀 테스트로 유지하고, 이 이슈에서 100-Worker Burst와 1,000-Job Queue 소진으로 범위를 확장한다.
3. 기본 개념
- Task: Worker가 실행할 코드 단위다. 여기서는 Job을 Claim하는 코드다.
- Executor: Thread Pool을 관리하며 Task를 실제 Thread에서 실행한다.
- readyLatch: 모든 Task가 출발 준비를 끝냈는지 확인하는 카운터다.
- startLatch: 준비된 Task를 거의 동시에 출발시키는 시작 신호다.
- Claim: 처리할 Job을 가져와 현재 Worker의 처리 권한과 소유권을 기록하는 동작이다.
Executor가 Task들을 실행
→ 각 Task가 readyLatch 감소
→ 각 Task가 startLatch 앞에서 대기
→ 모든 Task가 준비되면 startLatch 개방
→ 모든 Task가 거의 동시에 Claim 시도
4. 포함 범위
- 실제 OpenSQL 전용 격리 스키마
- Spring Transaction Proxy를 통과하는 실제
EmbeddingJobClaimService 호출
- 단일 PENDING Job에 ACTIVE Worker 100개 동시 Claim
- PENDING Job 1,000개를 ACTIVE Worker 20개가 반복 Claim
- 응답 Job ID 중복·누락 검증
- 최종 Job 상태, Worker, Token, Lease 검증
- Job별
LOCKED 이벤트 정확히 한 건 검증
- Thread 준비·동시 시작·Timeout·Executor 종료 제어
- 무거운 동시성 테스트용 전용 Gradle Task
- 직접 재현 가능한 환경 준비·명령어·DB 확인 SQL·실패 진단 가이드
5. 제외 범위
- TPS, 평균, P95, P99 성능 판정
- Worker 수별 처리량 비교
- DB Lock 대기 시간 측정
- Lease 만료 복구와 재Claim
- Claim Token 기반 오래된 Worker 차단
- Polling, 파싱, 청킹, 임베딩 실행
- 운영 코드 변경
6. 시나리오 A — 단일 Job 경쟁
PENDING Job 1개
ACTIVE Worker 100개
동시 Claim 100회
기대 결과:
- 성공 1건
- 빈 결과 99건
- 예외 0건
- 최종 상태 PROCESSING
- 응답 Worker/Token/Lease와 DB 값 일치
- LOCKED 이벤트 1건
7. 시나리오 B — 다중 Job 완전 소진
PENDING Job 1,000개
ACTIVE Worker 20개
각 Worker는 빈 결과가 나올 때까지 반복 Claim
기대 결과:
- 성공 응답 1,000건
- 고유 Job ID 1,000개
- 중복 Claim 0건
- 누락 Job 0건
- PENDING 0건
- PROCESSING 1,000건
- Worker/Token/Lease 누락 0건
- LOCKED 이벤트 1,000건
- Job별 이벤트 중복·누락 0건
8. 테스트 구조
- 신규
EmbeddingJobClaimConcurrencyIntegrationTest
@Tag("integration"), @Tag("claim-concurrency")
- 전용 스키마
docgrid_embedding_job_claim_concurrency_test
CountDownLatch로 모든 Worker 준비 후 동시 시작
- Worker Thread 100개와 DB Connection Pool을 분리해 제한된 Pool에서도 Burst 정합성 검증
- 테스트 중 Worker가 DEAD로 판정되지 않도록 테스트 전용 DEAD 기준 사용
- 전용
claimConcurrencyTest Gradle Task로 일반 단위 테스트와 분리
9. 검증 원칙
- 응답 개수만 보지 않고 초기 Job ID 집합과 Claim 응답 ID 집합을 비교한다.
- 응답의 Worker/Token/Lease를 최종 DB Row와 Job별로 비교한다.
- Worker별 Claim 건수는 관찰하지만 공정성 합격 기준으로 사용하지 않는다.
- 전체 실행 시간은 참고값으로만 기록하고 성능 기준으로 사용하지 않는다.
- 동시성 테스트가 실패하면 Assertion을 약화하지 않고 원인과 재현 정보를 남긴다.
10. 완료 기준
- 두 시나리오가 실제 OpenSQL에서 통과한다.
- 전용 Task가 5회 연속 통과한다.
- 전체 Build가 통과한다.
- Thread Timeout, 교착, Connection 획득 실패가 없다.
- 테스트 환경, 전체 명령어, 예상 결과, DB 확인 SQL, 실패 진단 절차가 문서화된다.
- Production 코드 변경 없이 테스트와 실행 인프라·문서만 추가한다.
11. 테스트 결과 문서
실행 명령어, 결과, DB 확인 SQL, 실패 진단 방법은 docs/test-results/gimin-#52-embedding-job-claim-concurrency.md에 기록한다.
1. 목적
여러 Worker가 동시에 PENDING Embedding Job을 Claim해도 Job 소유권이 정확히 한 번만 배정되고, 대량 Queue가 중복과 누락 없이 완전히 소진되는지 실제 OpenSQL에서 검증한다.
이 이슈는 Claim TPS나 P95를 측정하는 성능 테스트가 아니라 동시성 정합성 테스트다. 처리량과 Lock 경합 비교는 별도의 성능 테스트에서 다룬다.
2. 검증 대상 기능
다음 Job Claim 동작을 검증 대상으로 사용한다.
SELECT ... FOR UPDATE SKIP LOCKEDlocked_by_worker_idclaim_tokenlocked_at,lock_expires_atLOCKED이벤트 저장기존 2-Worker/1-Job 통합 테스트는 최소 회귀 테스트로 유지하고, 이 이슈에서 100-Worker Burst와 1,000-Job Queue 소진으로 범위를 확장한다.
3. 기본 개념
4. 포함 범위
EmbeddingJobClaimService호출LOCKED이벤트 정확히 한 건 검증5. 제외 범위
6. 시나리오 A — 단일 Job 경쟁
기대 결과:
7. 시나리오 B — 다중 Job 완전 소진
기대 결과:
8. 테스트 구조
EmbeddingJobClaimConcurrencyIntegrationTest@Tag("integration"),@Tag("claim-concurrency")docgrid_embedding_job_claim_concurrency_testCountDownLatch로 모든 Worker 준비 후 동시 시작claimConcurrencyTestGradle Task로 일반 단위 테스트와 분리9. 검증 원칙
10. 완료 기준
11. 테스트 결과 문서
실행 명령어, 결과, DB 확인 SQL, 실패 진단 방법은
docs/test-results/gimin-#52-embedding-job-claim-concurrency.md에 기록한다.