Skip to content

[Feat] PENDING Job Claim 및 Lease Lock 구현 - #49

Merged
Gimini-3 merged 2 commits into
developfrom
feature/47
Jul 22, 2026
Merged

[Feat] PENDING Job Claim 및 Lease Lock 구현#49
Gimini-3 merged 2 commits into
developfrom
feature/47

Conversation

@Gimini-3

@Gimini-3 Gimini-3 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

목적

등록된 인덱싱 Worker가 PENDING Embedding Job 하나를 중복 없이 Claim하고, DB Transaction이 끝난 뒤에도 현재 소유권을 확인할 수 있도록 Lease와 Claim Token을 발급합니다.

이 PR은 Job을 실제로 파싱·청킹·임베딩하는 실행 단계가 아니라, 후속 실행을 시작하기 전에 필요한 작업 소유권 획득 단계를 구현합니다.

Closes #47

구현 범위

  • POST /admin/indexing-jobs/claim?workerId={workerId} 관리자 API
  • 등록된 Worker 존재 여부와 Heartbeat 기준 실질 상태 검증
  • PENDING Job 우선순위 Queue 조회
    • priority DESC
    • created_at ASC
    • id ASC
  • PostgreSQL FOR UPDATE SKIP LOCKED 기반 동시 Claim 제어
  • Claim 성공 시 하나의 Transaction 안에서 다음 상태를 함께 기록
    • PROCESSING 상태 전이
    • 소유 Worker
    • locked_at
    • lock_expires_at
    • 매 Claim마다 새로 발급하는 UUID claim_token
  • 같은 Transaction에 LOCKED 인덱싱 이벤트 저장
  • Worker Lease 기간 설정 및 시작 시 유효성 검증
  • Claim 응답 DTO와 오류 코드 추가
  • Flyway Migration으로 claim_token 컬럼 추가

동작 흐름

1. ADMIN 요청이 Security Filter를 통과한다.
2. Controller가 workerId를 Claim Command Service에 전달한다.
3. Spring Transaction Proxy가 DB Transaction을 시작한다.
4. 주입된 Clock으로 Claim 기준 시각을 한 번만 계산한다.
5. Worker를 조회하고 ACTIVE/IDLE 및 Heartbeat 만료 여부를 검증한다.
6. PENDING Queue에서 최우선 Job 한 건을 FOR UPDATE SKIP LOCKED로 조회한다.
7. Job이 없거나 모든 후보가 다른 Transaction에 잠겨 있으면 빈 결과를 반환한다.
8. Job이 있으면 PROCESSING 상태, Worker, UUID Token, Lease 시간을 함께 반영한다.
9. 동일 Transaction에 LOCKED 이벤트를 저장한다.
10. JPA Dirty Checking이 Job 변경을 반영하고 Transaction을 Commit한다.
11. Claim 성공은 200, 처리 가능한 후보 없음은 204로 응답한다.

Worker 검증 실패 시에는 Queue 행 잠금을 시도하지 않습니다. Claim 도중 예외가 발생하면 Job 변경과 이벤트 저장이 함께 Rollback되고, 획득한 DB 행 잠금도 Transaction 종료와 함께 해제됩니다.

동시성 설계

장치 수명 역할
DB 행 잠금 Claim Transaction 종료까지 같은 순간에 여러 Worker가 같은 Job을 선택하지 못하게 함
Lease (lock_expires_at) 설정된 Lease 기간 Commit 이후 Worker 장애를 복구할 수 있는 시간 기준 제공
Claim Token 다음 Claim에서 교체될 때까지 Lease 만료 후 과거 Worker가 보낸 늦은 결과를 현재 소유권과 구분

일반 FOR UPDATE는 최우선 Job의 잠금이 풀릴 때까지 뒤 Worker를 대기시켜 Queue 전체 처리량을 떨어뜨릴 수 있습니다. SKIP LOCKED는 이미 경쟁 중인 행을 기다리지 않고 다음 후보를 선택하므로 여러 Worker가 서로 다른 Job을 병렬로 Claim할 수 있습니다.

DB 행 잠금은 파싱·청킹·임베딩 전체 시간 동안 유지하지 않습니다. 외부 처리 시간을 DB Transaction에 포함하면 Connection과 행 잠금을 장시간 점유하므로, 이 PR에서는 소유권 기록 직후 Commit하고 실제 인덱싱은 후속 작업에서 수행하도록 경계를 나눴습니다.

응답 및 오류

  • Claim 성공: 200 OK
  • 현재 Claim할 Job 없음: 204 No Content
  • Worker 없음: 404 WORKER-001
  • STOPPED/DEAD 또는 Heartbeat 만료 Worker: 409 WORKER-002
  • /admin/** 기존 정책에 따라 ADMIN 이외 요청: 403 Forbidden

응답에는 Entity를 직접 노출하지 않고 Job, Worker, 문서 버전, Embedding Model 식별자와 Token·Lease 정보만 담습니다.

설계 문서와 코드 주석

  • Claim과 실제 Job 실행의 경계
  • Worker·Thread·Polling·Job의 관계
  • DB 행 잠금·Lease·Claim Token의 차이
  • Spring @Transactional, JPA Managed Entity, Dirty Checking 내부 동작
  • PostgreSQL READ COMMITTEDSKIP LOCKED 동작
  • 대안 비교와 SKIP LOCKED 선택 이유
  • 장애 발생 시점별 Commit/Rollback 결과
  • 실제 OpenSQL 동시성 테스트 구성과 호출 그래프

변경된 클래스에는 역할과 책임 경계를 설명하는 클래스 주석을 추가했습니다. Claim처럼 순서가 중요한 흐름에는 1., 2., 3., 4. 주석을 사용했고, 핵심 줄에는 문법 설명이 아니라 불변식과 선택 이유가 드러나도록 보강했습니다. 이 규칙은 AGENTS.md에도 기록했습니다.

테스트

  • 실제 OpenSQL 환경 전체 빌드: ./gradlew clean build — 192 tests 통과
  • 최종 주석 보강 후 테스트 소스 컴파일: ./gradlew compileTestJava 통과
  • 최종 주석 보강 후 핵심 단위 테스트 통과
    • IndexingJobAdminControllerTest
    • EmbeddingJobConverterTest
    • EmbeddingJobTest
    • EmbeddingJobClaimServiceTest
    • IndexingWorkerPropertiesTest
  • OpenSQL 통합 테스트에서 다음 동시성 조건 검증
    • 잠긴 최우선 행을 기다리지 않고 다음 Job 선택
    • 두 Worker가 Job 하나를 동시에 Claim할 때 정확히 하나만 성공
    • Queue 우선순위 및 FIFO 정렬
    • DB 상태와 LOCKED 이벤트의 단일 소유자 Commit

후속 범위

다음 항목은 이 PR에 포함하지 않습니다.

  • Polling Scheduler와 실행 Thread Pool
  • Lease 갱신
  • 만료 Lease 회수와 재Claim
  • Claim Token을 검증하는 완료·실패 API
  • Job Attempt 이력
  • 파일 파싱, 청킹, Embedding 호출, Vector 저장

Summary by CodeRabbit

  • 새로운 기능

    • 관리자가 인덱싱 작업을 워커에 안전하게 할당할 수 있는 API를 추가했습니다.
    • 작업 상태, 담당 워커, 클레임 토큰 및 Lease 만료 정보를 응답으로 제공합니다.
    • 처리 가능한 작업이 없으면 정상적으로 204 No Content를 반환합니다.
    • 워커 상태에 따라 작업 할당 가능 여부와 오류를 구분해 안내합니다.
  • 안정성 개선

    • 여러 워커가 동일한 인덱싱 작업을 중복 처리하지 않도록 동시 할당을 방지합니다.
    • Lease 기간 설정을 검증하고, 할당 이력을 기록합니다.
  • 문서화

    • 작업 할당 및 Lease 운영 방식과 관련 엔지니어링 가이드를 문서화했습니다.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e8d89724-d10d-40aa-bc7e-17961642964d

📥 Commits

Reviewing files that changed from the base of the PR and between b2bff93 and eded7f1.

📒 Files selected for processing (24)
  • .dev/learnings/embedding-job-skip-locked-claim.md
  • AGENTS.md
  • docs/design/gimin-#47-embedding-job-claim-lease.md
  • 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/ClaimedEmbeddingJobResponse.java
  • src/main/java/com/opensource/docgrid/domain/embedding/entity/EmbeddingJob.java
  • src/main/java/com/opensource/docgrid/domain/embedding/repository/EmbeddingJobRepository.java
  • src/main/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobClaimService.java
  • src/main/java/com/opensource/docgrid/domain/worker/config/IndexingWorkerProperties.java
  • src/main/java/com/opensource/docgrid/domain/worker/repository/IndexingEventRepository.java
  • src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java
  • src/main/resources/application-test.yml
  • src/main/resources/application.yml
  • src/main/resources/db/migration/V33__add_embedding_job_claim_token.sql
  • src/test/java/com/opensource/docgrid/domain/document/integration/DocumentUploadIntegrationTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/controller/IndexingJobAdminControllerTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/converter/EmbeddingJobConverterTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/entity/EmbeddingJobTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/integration/EmbeddingJobClaimIntegrationTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/integration/EmbeddingModelConstraintIntegrationTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/repository/EmbeddingModelRepositoryTest.java
  • src/test/java/com/opensource/docgrid/domain/embedding/service/command/EmbeddingJobClaimServiceTest.java
  • src/test/java/com/opensource/docgrid/domain/worker/config/IndexingWorkerPropertiesTest.java

📝 Walkthrough

Walkthrough

Changes

이번 변경은 PENDING Embedding Job의 원자적 Claim과 Lease 소유권을 구현한다. Worker 상태 검증, FOR UPDATE SKIP LOCKED, Claim Token·Lease 기록, LOCKED 이벤트 저장, 관리자 Claim API 및 동시성 테스트를 추가했다.

Embedding Job Claim

Layer / File(s) Summary
Claim 상태와 데이터 계약
src/main/java/.../embedding/entity/EmbeddingJob.java, src/main/resources/db/migration/... , src/main/java/.../dto/response/*
EmbeddingJobPENDING에서 PROCESSING으로 전환되며 Worker, Claim Token, Lease 시각을 기록하고, claim_token 컬럼과 응답 DTO를 추가했다.
잠금 기반 Claim 서비스
src/main/java/.../embedding/repository/*, src/main/java/.../embedding/service/command/*, src/main/java/.../worker/*, src/main/resources/application.yml
Worker의 Heartbeat와 상태를 검증한 뒤 우선순위 순으로 PENDING Job을 FOR UPDATE SKIP LOCKED로 선택하고, 상태·Lease·LOCKED 이벤트를 트랜잭션에서 저장한다.
관리자 Claim API
src/main/java/.../embedding/controller/*, src/main/java/.../embedding/converter/*, src/main/java/.../global/exception/ErrorCode.java
POST /admin/indexing-jobs/claim을 추가해 성공 시 200, Job이 없을 때 204, Worker 오류 시 404/409를 반환한다.
Claim 및 저장소 검증
src/test/java/.../embedding/*, src/test/java/.../worker/*, src/test/java/.../document/*, src/test/java/.../integration/*, src/main/resources/application-test.yml, docs/design/*, .dev/learnings/*, AGENTS.md
Entity·Service·Controller 단위 테스트와 REQUIRES_NEW 멀티스레드 PostgreSQL 통합 테스트를 추가하고, 테스트 스키마·설계 문서·주석 규칙을 갱신했다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant Controller
  participant ClaimService
  participant PostgreSQL
  participant EventRepository
  Admin->>Controller: POST claim(workerId)
  Controller->>ClaimService: claim(workerId)
  ClaimService->>PostgreSQL: validate worker and select PENDING with SKIP LOCKED
  PostgreSQL-->>ClaimService: locked job
  ClaimService->>PostgreSQL: update PROCESSING, worker, token, lease
  ClaimService->>EventRepository: save LOCKED event
  EventRepository->>PostgreSQL: persist event
  ClaimService-->>Controller: ClaimedEmbeddingJobResponse
  Controller-->>Admin: 200 or 204
Loading

Possibly related PRs

  • DocGrid/backend#6: 기존 EmbeddingJob 및 IndexingEvent 구조와 Claim 전환에 연결된다.
  • DocGrid/backend#14: Embedding Model 제약 통합 테스트의 정리 로직 변경과 연결된다.
  • DocGrid/backend#40: Worker 상태 및 Heartbeat 기반 Claim 가능성 검증과 연결된다.

Suggested labels: ✨ Feature

✨ 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/47

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
src/main/resources/application-test.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

src/main/resources/application.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] PENDING Job Claim 및 Lease Lock 구현

1 participant