Skip to content

[Feat] Chunk Embedding 생성 및 Vector 저장 #82

Description

@Gimini-3

📌 Description

현재 Worker가 소유한 PROCESSING Embedding Job과 STARTED Attempt를 실행 Context로 검증한 뒤,
Job이 직접 참조하는 CHUNKED Document Version의 Chunk를 순서대로 실제 BAAI/bge-m3 임베딩 서버에 전달하고
1024차원 Vector를 embeddings에 원자적으로 저장합니다.

임베딩 서버 호출 동안 DB Transaction과 행 잠금을 유지하지 않습니다. 준비·완료 단계에서 Job과 Version을
같은 순서로 잠그고 Worker, Claim Token, Lease와 Attempt를 다시 검증하여 과거 Worker의 늦은 결과 저장을
차단합니다. 응답 유실과 동시 재호출은 중복 Vector를 만들지 않고 같은 Embedding Set으로 수렴해야 합니다.

선행 문서의 Mock Embedding 표현과 달리 현재 기준선에는 실제 BAAI/bge-m3 FastAPI 서버와
vector(1024) Schema가 있으므로 가짜 Vector가 아니라 기존 /embed 계약을 사용합니다.

✅ To-do

외부 Embedding 호출 계약

  • 모델 선택과 HTTP 전송을 분리하는 EmbeddingClient 추가
  • 기존 QueryEmbeddingService가 공통 Client를 사용하도록 위임
  • Document Embedding은 현재 Active 모델을 다시 고르지 않고 Job의 embedding_model_id를 사용
  • /embed 응답의 null Vector, 차원 불일치와 비유한 값(NaN/Infinity) 거부
  • 서버 연결·Timeout·5xx를 기존 EMBEDDING_SERVER_UNAVAILABLE 계약으로 변환
  • 외부 요청·응답과 로그에 Claim Token, Chunk 본문, Vector를 노출하지 않음

준비 Transaction

  • Embedding Job을 먼저 쓰기 잠금으로 조회
  • 공통 EmbeddingJobOwnershipValidator로 PROCESSING 상태, Worker, Claim Token과 Lease 검증
  • Path Attempt ID가 현재 Claim의 STARTED Attempt 및 Worker와 일치하는지 검증
  • Job이 직접 참조하는 Document Version을 다음 순서로 쓰기 잠금
  • Version이 CHUNKED이거나 재개 가능한 EMBEDDING 상태인지 검증
  • Chunk를 chunk_index ASC로 조회하고 0부터 연속이며 한 건 이상인지 검증
  • Job에 고정된 모델 ID·차원과 Chunk ID·Index·본문을 불변 Snapshot으로 반환
  • 최초 요청만 CHUNKED → EMBEDDING 전환과 EMBEDDING_STARTED 이벤트 저장
  • 현재 모델의 Embedding Set이 이미 완성됐으면 외부 호출 없이 멱등 결과 재생
  • 일부 Embedding만 있거나 Version 상태와 저장 결과가 모순이면 데이터 불일치 오류

외부 작업과 Draft

  • Transaction 밖에서 Chunk Index 순서대로 기존 /embed Endpoint 호출
  • 응답 Vector 길이가 Job 모델의 dimension과 같은지 매 Chunk 검증
  • Vector의 각 원소가 유한한 값인지 검증
  • Vector Hash를 IEEE-754 float Byte의 SHA-256 소문자 Hex로 결정적으로 계산
  • Chunk ID·Index·Vector·Dimension·Hash만 가진 불변 Draft 생성

완료 Transaction과 저장

  • Job → Version 순서로 다시 잠금 획득
  • 새 현재 시각으로 Worker, Claim Token, Lease와 Attempt 재검증
  • 준비 단계의 Version·Model·Chunk Set과 현재 DB 상태가 같은지 재검증
  • 각 Chunk의 document_id, document_version_id, embedding_model_id 역정규화 값 검증
  • 모든 Embedding을 ACTIVEsaveAllAndFlush
  • (chunk_id, embedding_model_id) Unique Constraint를 최종 중복 방어선으로 유지
  • Embedding 전체 저장 중 하나라도 실패하면 모두 Rollback
  • 동시 후발 요청은 잠금 뒤 완성된 기존 결과를 확인하고 200으로 재생
  • Version은 EMBEDDING으로 유지하고 Attempt·Job·Document 완료 처리는 후속 기능에 위임

Repository·API

  • DocumentChunkRepository에 Version별 chunk_index ASC 조회 추가
  • EmbeddingRepository에 Version·Model별 존재·개수 조회와 전체 저장 계약 추가
  • POST /admin/indexing-jobs/{jobId}/attempts/{attemptId}/embeddings 추가
  • 요청은 Worker ID와 canonical UUID Claim Token만 입력
  • 응답은 Job·Attempt·Version·Model ID, Chunk 수, Embedding 수와 Version 상태만 반환
  • 최초 저장은 201 Created, 기존 완성 결과 재생은 200 OK
  • 기존 /admin/** ADMIN 정책 재사용 및 Swagger 성공·오류 계약 문서화

검증

  • Client 정상·서버 장애·null·차원 불일치·NaN/Infinity 단위 테스트
  • 준비·완료 Transaction 상태·이벤트·불변식·Rollback 단위 테스트
  • Orchestration이 외부 호출 중 DB Transaction을 유지하지 않는 계약 테스트
  • Controller 입력 검증, 201/200, ADMIN/USER/미인증 계약 테스트
  • 실제 OpenSQL에서 1024차원 pgvector 저장·연관·Hash 검증
  • 실제 OpenSQL에서 두 Thread 동시 요청이 한 Embedding Set으로 수렴하는지 검증
  • 재호출 시 외부 Client, Row와 이벤트가 증가하지 않는지 검증
  • ./gradlew clean testgit diff --check 통과
  • docs/design/에 실제 구현과 일치하는 상세 설계 문서 작성

🔒 핵심 불변식

  • 처리 모델은 호출 시점의 Active 모델이 아니라 Job 생성 시 고정된 모델입니다.
  • 모든 관련 상태 변경은 Job을 먼저, Version을 다음 순서로 잠급니다.
  • 외부 임베딩 서버 호출 중 DB Transaction과 행 잠금을 유지하지 않습니다.
  • 외부 작업 전후에 현재 Claim 소유권과 Lease를 검증합니다.
  • 같은 Version·Model의 Chunk 수와 Embedding 수는 완료 시 정확히 같습니다.
  • 같은 Chunk·Model 조합의 Embedding은 한 건만 존재합니다.
  • 저장된 Embedding의 Document·Version·Model은 Chunk와 Job의 연관관계와 일치합니다.
  • Claim Token, Chunk 본문과 Vector는 API 응답·이벤트·일반 로그에 노출하지 않습니다.

🚫 제외 범위

  • Attempt SUCCESS 전환
  • Embedding Job과 Document Version의 INDEXED 완료
  • documents.current_version_id 교체와 Document 완료 상태 전환
  • 실패 시 Attempt·Job·Version 종료 및 자동 재시도
  • Lease 연장, 만료 Job 회수와 Worker 자동 Polling
  • FastAPI Batch Endpoint 및 병렬 Embedding 최적화
  • PDF·DOCX·HTML Parser와 의미 Chunking
  • 새 Flyway Migration, Message Broker, Cache 및 Production Dependency

✅ 완료 기준

  • 유효한 현재 Claim만 CHUNKED Version의 모든 Chunk를 Job 모델로 Vector화할 수 있습니다.
  • 외부 호출 중 DB Connection과 Job·Version Lock을 점유하지 않습니다.
  • Vector 차원·유한성·연관관계·Chunk 순서 불변식이 저장 전에 검증됩니다.
  • Embedding 전체가 한 Transaction으로 저장되고 부분 결과가 남지 않습니다.
  • 순차·동시 재호출이 외부 호출과 Row·이벤트를 중복 생성하지 않습니다.
  • 오래된 Token, 다른 Worker, 잘못된 Attempt와 만료 Lease는 Vector를 저장하지 못합니다.
  • Version은 EMBEDDING 상태로 남아 후속 인덱싱 완료 기능이 이어서 처리할 수 있습니다.
  • 단위·Controller·OpenSQL 통합·동시성·전체 회귀 테스트가 통과합니다.

📒 기타

  • 선행 기능: [Feat] 텍스트 파싱 및 Chunk 저장 #68
  • 기존 실제 모델: BAAI/bge-m3, 1024차원, COSINE
  • 기존 Vector Schema와 Unique Constraint를 재사용하므로 Migration은 추가하지 않습니다.
  • 현재 FastAPI /embed가 단건 요청만 지원하므로 정확성 기준선은 Chunk 순차 호출로 구현하고,
    처리량 개선을 위한 Batch API와 Lease 연장은 별도 범위로 분리합니다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions