Skip to content

RagService Chunking·로컬 임베딩·COPY 적재 ​

목적과 보안 경계 ​

이 파이프라인은 FileStore 또는 사용자 포탈이 승인해 발행한 원본 문서를 다음 순서로 처리한다.

  1. 프라이빗 문서 정제기가 Markdown 텍스트와 표를 생성한다.
  2. SemanticChunker가 의미 경계 우선 방식으로 기본 500자 chunk / 100자 overlap JSONL을 생성한다.
  3. 각 chunk는 로컬 임베딩 API(Ollama bge-m3 기본값)에 배치 전송되고, 정확히 1024차원인지 확인된다.
  4. RLS principal별로 분리한 PostgreSQL transaction에서 temporary staging table로 COPY한다.
  5. 같은 transaction에서 source_documents, doc_chunks_vector에 upsert한다.

RLS tenant·조직·보안등급은 browser/CLI가 임의로 승격할 수 있는 입력이 아니다. FileStore/PortalConsole가 서명·검증한 metadata에서만 만들고, database connection에는 set_config(..., true)로 transaction-local 적용한다.

Chunk JSON 패키지 ​

각 JSONL record는 vector와 함께 저장할 최소 원본 메타데이터를 가진다.

필드설명
chunk_id, document_id, chunk_index멱등 upsert와 출처 추적 식별자
chunk_text, char_start, char_end, token_count_approx의미 단위와 위치 정보
tenant_id, organization_id, security_levelPostgreSQL RLS 및 검색 경계
metadata.classification, metadata.title문서 분류와 제목
metadata.registrant등록자 ID·이름·소속 부서 코드
metadata.registeredAt, metadata.sourceVersion등록일과 원본 버전
metadata.assetId, metadata.portalIdFileStore/소유 포탈 추적 정보

1. JSONL chunk 생성 ​

metadata.json은 FileStore나 포탈 backend가 생성한다. 일반 브라우저가 tenant, organization, security level을 제출하는 방식은 허용하지 않는다.

json
{
  "source_document_id": "0a998a31-73d3-4901-9b05-318d6344d1d4",
  "tenant_id": "18a532ad-c7bb-4b7d-9fb7-87fbe704f2e8",
  "organization_id": "fa9e9751-11e5-4332-b678-52669f06803e",
  "classification": "업무 지침",
  "title": "보안 운영 표준",
  "registrant_id": "directory-admin-1",
  "registrant_name": "홍길동",
  "registrant_department_code": "SEC-OPS",
  "registered_at": "2026-08-25T12:00:00Z",
  "security_level": "CONFIDENTIAL",
  "source_version": "asset-v14",
  "source_type": "ASSET_ATTACHMENT",
  "asset_id": "9e244110-3ddb-4ca4-a3e5-1c2b7552bf1b",
  "extra": {"portalId": "process-center"}
}
bash
cd RagService
python3 scripts/chunk_document.py cleaned.md \
  --metadata metadata.json \
  --output chunks.jsonl \
  --chunk-size 500 \
  --overlap 100

2. 로컬 임베딩과 PostgreSQL COPY 적재 ​

requirements.txt를 설치한 뒤, pgvector/RLS schema와 로컬 embedding server가 준비된 환경에서 실행한다.

bash
sudo pip3 install -r requirements.txt

PRIVATE_EMBEDDING_URL=http://127.0.0.1:11434 \
PRIVATE_EMBEDDING_PROVIDER=ollama \
PRIVATE_EMBEDDING_MODEL=bge-m3 \
PRIVATE_EMBEDDING_DIMENSIONS=1024 \
B2B_RLS_DATABASE_URL='postgresql://processcenter:***@127.0.0.1:5432/processcenter' \
python3 -m ragcenter.indexing chunks.jsonl --batch-size 16

Ollama에서는 /api/embed와 embeddings[]를, OpenAI 호환 로컬 endpoint에서는 /v1/embeddings와 data[].embedding을 사용한다. 응답 개수·numeric value·1024차원이 맞지 않으면 database transaction 전에 즉시 중단한다.

COPY와 RLS 안전성 ​

대량 chunk를 SQL 문자열로 이어 붙이지 않는다. Python psycopg의 row-based COPY FROM STDIN으로 temporary rag_chunk_stage에 적재한 뒤, parameterized transaction으로 최종 upsert한다. 하나의 RLS transaction에는 하나의 (tenant_id, organization_id, security_level) principal만 포함되며, 여러 principal의 JSONL은 embedding 후 자동으로 그룹 분리한다.

이 방식은 RLS candidate leakage를 방지하고, retry 시 (document_id, chunk_index) unique key로 동일 문서 버전의 vector를 갱신한다. 원본 파일 다운로드 URL과 object-storage signed URL은 JSONL 또는 RAG event에 넣지 않으며, RagService는 FileStore의 상호 인증 content retrieval API만 사용해야 한다.

검증 ​

bash
# deterministic chunk/metadata/embedding contract tests
python3 -m unittest tests/test_chunking.py

# running PostgreSQL+pgvector and Ollama bge-m3 stack required
python3 -m unittest tests/test_local_copy_pipeline.py

두 번째 통합 테스트는 실제로 chunk를 생성하고, Ollama에서 1024차원 embedding을 받고, RLS가 적용된 doc_chunks_vector에 COPY한 뒤 application DB role로 조회해 차원과 행 수를 확인한다.