Field Log · Entry

Enterprise RAG 지식 수명주기: 수집·변환·검증·배포를 한 파이프라인으로 (1/10)

Enterprise RAG 원문을 immutable snapshot, canonical document, chunk, retrieval index로 변환하고 manifest와 active release로 추적하는 지식 수명주기

오늘의 결론

  • Enterprise RAG의 지식 기반은 “파일을 읽어 vector DB에 넣은 결과”가 아닙니다. 원문 revision에서 사용자에게 공개된 index generation까지 추적 가능한 versioned data product입니다.
  • Raw snapshot, canonical document, chunk dataset, retrieval index를 분리합니다. 원문 보존·파서 재실행·청킹 실험·인덱스 교체의 수명이 서로 다르기 때문입니다.
  • upsert만으로는 부족합니다. Source change event에는 생성·수정뿐 아니라 삭제·권한 변경이 포함되고, at-least-once 전달을 견디도록 stable identity와 idempotency가 필요합니다.
  • 새 index를 쓰기 시작했다고 곧바로 공개하지 않습니다. 완전성·구조·ACL·검색 품질·신선도 gate를 통과한 candidate generation만 active pointer로 원자적으로 전환합니다.
  • 모든 실행은 source snapshot, code commit, parser·schema·chunker·embedding·index 설정을 manifest로 남깁니다. 그래야 “왜 이 답변에 이 근거가 나왔는가”를 재현하고 rollback할 수 있습니다.

앞 시리즈 마지막 글에서는 모델 serving을 재현 가능한 workload와 release gate로 운영했습니다. 이제 RAG Agent가 읽는 지식도 같은 수준의 release 단위로 다룹니다. 문서 수집과 파싱 입문이 검색 가능한 문서를 만드는 첫 단계였다면, 이번 시리즈는 그 결과를 변경·권한·삭제까지 포함해 지속 운영하는 방법을 다룹니다.

source change
  → capture immutable evidence
  → normalize into a canonical document
  → derive stable chunks and metadata
  → build candidate sparse/vector indexes
  → validate completeness, security, quality, freshness
  → atomically publish an active generation
  → observe, supersede, delete, or roll back

이 글에서 답하는 질문

  1. 왜 RAG ingestion을 ETL job이 아니라 지식 수명주기로 봐야 하는가?
  2. Raw·canonical·chunk·index 계층은 어떤 책임을 갖는가?
  3. Snapshot과 CDC event를 어떻게 이어 누락과 중복을 막는가?
  4. 재실행 가능한 run manifest에는 무엇을 기록해야 하는가?
  5. Candidate index를 언제 사용자에게 공개할 수 있는가?
  6. 반도체 설비 문서처럼 revision과 권한이 중요한 자료를 어떻게 모델링하는가?

Enterprise RAG source event가 raw snapshot, canonical document, stable chunk, candidate sparse와 vector index로 변환되고 completeness, structure, ACL, retrieval, freshness gate를 통과한 generation만 active pointer로 원자 배포되며 run manifest와 provenance가 전 단계를 연결하는 지식 수명주기

그림 1. Query plane은 “방금 쓰인 문서”가 아니라 검증을 통과한 active generation만 읽는다. 모든 파생물은 manifest를 통해 source revision까지 역추적된다.


1. 폴더 동기화와 지식 파이프라인은 다르다

가장 단순한 RAG ingestion은 다음처럼 보입니다.

for path in files:
    text = parse(path)
    for chunk in split(text):
        vector_db.upsert(embed(chunk), metadata={"path": path})

Demo에서는 충분합니다. 그러나 운영 환경에서 다음 질문에는 답하지 못합니다.

  • 같은 파일명이지만 내용이 바뀌면 기존 chunk는 어떻게 찾고 지우는가?
  • 파서 버그를 고쳤을 때 어느 문서부터 다시 계산하는가?
  • 문서 revision 7과 8의 근거가 같은 index에 섞이지 않았는가?
  • 접근 권한이 회수된 뒤 몇 분 안에 검색 결과에서 사라지는가?
  • 10만 건 중 314건이 파싱 실패했는데 나머지를 공개해도 되는가?
  • 새 embedding model의 recall이 낮으면 이전 index로 돌아갈 수 있는가?
  • 답변 citation을 source PDF의 정확한 page와 span으로 복원할 수 있는가?

이 질문은 embedding API보다 data identity, state transition, lineage와 release control의 문제입니다.

2. 지식 기반을 Derived Data Product로 정의한다

Search index는 원문 자체가 아니라 원문에서 계산한 파생물입니다.

source of truth
  └─ raw snapshot
      └─ canonical document
          └─ chunk dataset
              ├─ sparse index
              ├─ vector index
              └─ graph / auxiliary indexes

따라서 index가 유실돼도 source snapshot과 manifest에서 다시 만들 수 있어야 합니다. 반대로 index만 남고 어떤 parser·chunker·embedding으로 만들었는지 모르면 복구 가능한 자산이 아닙니다.

Apache Iceberg의 table spec은 immutable data file, snapshot, manifest와 atomic metadata swap으로 특정 시점의 table state를 표현합니다. RAG가 Iceberg를 반드시 써야 한다는 뜻은 아닙니다. 불변 파생물과 원자적 pointer를 분리하는 설계 원리를 지식 index에도 적용할 수 있다는 뜻입니다. Iceberg specification은 snapshot이 특정 시점의 전체 file set을 가리키고 commit이 새 metadata로 원자 전환되는 구조를 정의합니다.

3. 네 계층의 책임을 분리한다

Layer A. Raw Snapshot

수집 당시의 증거입니다.

source_uri: s3://manuals/etcher/alarm-guide.pdf
source_object_id: obj_018f...
source_revision: "sharepoint-etag:8f3a..."
fetched_at: 2026-07-16T01:02:03Z
event_time: 2026-07-15T23:48:10Z
content_digest: sha256:9a1c...
mime_type: application/pdf
byte_length: 18420391
acl_snapshot_id: acl_01j...

Raw layer에서는 텍스트를 예쁘게 고치지 않습니다. Parser 결과가 잘못됐을 때 원문 byte로 돌아가 재실행할 수 있어야 합니다.

Layer B. Canonical Document

도구별 출력 차이를 내부 표준 model로 정규화합니다.

Document
  ├─ stable document identity
  ├─ source revision and provenance
  ├─ ordered structural elements
  │    ├─ heading
  │    ├─ paragraph
  │    ├─ table / row / cell
  │    ├─ figure / caption
  │    └─ equation
  ├─ page and bounding boxes
  ├─ language and OCR confidence
  └─ inherited ACL and business metadata

Canonical layer는 Markdown 한 덩어리보다 풍부해야 합니다. 페이지·좌표·표 구조를 버리면 이후 citation과 재청킹에서 복구할 수 없습니다.

Layer C. Chunk Dataset

검색 실험의 입력입니다. Chunk는 stable span을 가리키는 파생 record여야 합니다.

{
  "chunk_id": "chk_...",
  "document_id": "doc_...",
  "document_version": 8,
  "span": {"element_start": 142, "element_end": 149},
  "text": "...",
  "heading_path": ["Alarm", "Vacuum", "E204"],
  "acl_ref": "acl_01j...",
  "chunker_revision": "semantic-v4",
  "content_digest": "sha256:..."
}

Text를 다시 split해서 우연히 같은 순번을 얻는 방식보다 canonical element span을 기준으로 identity를 만듭니다.

Layer D. Retrieval Index Generation

Sparse posting, dense vector, filter column, graph edge처럼 serving에 최적화된 구조입니다.

generation_id: kb-prod-20260716.4
chunk_dataset_snapshot: chunks-20260716.3
sparse:
  analyzer_revision: ko-domain-v5
dense:
  embedding_model: example-embed-v3
  dimension: 1536
  normalization: l2
ann:
  algorithm: hnsw
  m: 32
  ef_construction: 200
policy_schema: acl-v2
status: candidate

Index generation은 immutable하게 취급합니다. 설정을 제자리에서 바꿔 정체를 잃기보다 새 generation을 만들고 비교합니다.

4. Object, Version, Representation을 구분한다

파일 경로나 URL 하나를 document ID로 쓰면 rename과 이동에 취약합니다. 반대로 content hash만 쓰면 내용이 같은 서로 다른 정책 문서를 하나로 합칠 수 있습니다.

최소 세 identity를 구분합니다.

Identity의미바뀌는 조건
source_object_id원천 시스템의 논리 객체원천에서 새 객체로 생성될 때
document_version_id그 객체의 특정 business revision내용·유효성·ACL 등 version contract가 바뀔 때
representation_idparser·schema로 만든 표현parser·normalizer·schema가 바뀔 때

이 분리는 “문서는 그대로인데 parser만 고친 재처리”와 “source에서 실제 revision이 바뀐 수정”을 구별합니다. Stable ID와 dedup은 3편에서 더 깊게 다룹니다.

5. State Machine을 명시한다

Boolean indexed=true 하나로는 중간 실패와 삭제를 표현할 수 없습니다.

DISCOVERED
  → FETCHED
  → RAW_ACCEPTED
  → NORMALIZED
  → CHUNKED
  → INDEX_CANDIDATE
  → VALIDATED
  → ACTIVE
  → SUPERSEDED
  → RETIRED

side states:
  QUARANTINED · FAILED_RETRYABLE · FAILED_TERMINAL · DELETION_PENDING

각 transition에는 precondition과 artifact가 있어야 합니다.

transition: NORMALIZED -> CHUNKED
requires:
  - canonical_schema_valid
  - parser_status == accepted
  - acl_snapshot_present
produces:
  - chunk_dataset_partition
  - chunk_count
  - span_coverage_report
idempotency_key:
  - document_version_id
  - representation_id
  - chunker_revision

Worker 재시작 후 같은 transition을 반복해도 동일한 logical output을 얻어야 합니다.

6. Snapshot과 Change Stream을 이어 붙인다

처음에는 전체 corpus snapshot이 필요하고, 이후에는 변경 event가 필요합니다.

Initial Snapshot

enumerate all objects at watermark W0
  → fetch immutable revisions
  → process in resumable chunks
  → record scanned key range and failures

Incremental Stream

events after W0
  → UPSERT content
  → DELETE object
  → ACL_CHANGED
  → METADATA_CHANGED
  → RESTORE / RECLASSIFY

핵심 문제는 snapshot을 읽는 동안 발생한 변경입니다. Snapshot 완료 뒤 “현재 시각부터” stream을 시작하면 중간 변경을 잃습니다. Source가 제공하는 log position, monotonically comparable revision, event sequence 또는 watermark를 보존해야 합니다.

Debezium incremental snapshots는 table baseline을 chunk 단위로 읽는 동안 transaction log streaming을 계속하고, watermark로 snapshot progress를 추적합니다. 문서 repository가 database가 아니어도 baseline과 event stream을 끊김 없이 연결하고 resume cursor를 저장한다는 원리는 같습니다.

7. At-least-once를 기본으로 설계한다

Distributed pipeline에서 exactly-once delivery를 가정하면 retry가 곧 중복이 됩니다. 현실적인 계약은 다음과 같습니다.

delivery: at least once
processing: idempotent per logical transition
publication: atomic per generation

Event handler는 source event ID만으로 dedup하지 않습니다. Source가 같은 revision을 다른 event ID로 재전송할 수 있기 때문입니다.

def handle(event):
    key = (
        event.source_object_id,
        event.source_revision,
        PIPELINE_CONTRACT_HASH,
    )

    if artifact_store.exists(key):
        return artifact_store.result(key)

    raw = fetch_and_verify(event)
    result = build_derived_artifacts(raw)
    artifact_store.commit_if_absent(key, result)
    return result

Side effect보다 artifact write를 먼저 설계하고, commit_if_absent나 compare-and-swap으로 경쟁 실행을 수렴시킵니다.

8. Event Envelope에 들어갈 최소 필드

{
  "event_id": "evt_01j...",
  "event_type": "ACL_CHANGED",
  "source_system": "manual-repository",
  "source_object_id": "obj_018f...",
  "source_revision": "rev-8",
  "event_time": "2026-07-15T23:48:10Z",
  "observed_time": "2026-07-16T01:02:03Z",
  "sequence": "partition-4:938102",
  "payload_ref": "s3://events/...",
  "trace_id": "tr_01j..."
}

CloudEvents는 event의 source, type, subject, time와 data를 공통 envelope로 표현하는 공개 specification을 제공합니다. 구현을 그대로 채택하지 않더라도 event metadata와 domain payload를 분리하는 기준으로 쓸 수 있습니다. CloudEvents specification을 참고하세요.

9. Event Time과 Processing Time을 둘 다 남긴다

문서가 source에서 바뀐 시각과 pipeline이 감지한 시각은 다릅니다.

event_time     = source에서 변경이 유효해진 시각
observed_time  = connector가 변경을 본 시각
processed_time = canonical artifact를 만든 시각
indexed_time   = candidate index에 반영된 시각
active_time    = query plane에 공개된 시각

이를 이용해 지연을 분해합니다.

$$ freshness\ lag = active_time - event_time $$

capture lag   = observed_time - event_time
processing lag= indexed_time - observed_time
release lag   = active_time - indexed_time

신선도 문제를 발견했을 때 connector, parser backlog, embedding queue, validation gate 중 어디가 느린지 알 수 있습니다.

10. Run Manifest가 재현성의 중심이다

실행마다 다음 manifest를 불변 저장합니다.

run_id: run_01j...
trigger:
  type: incremental
  source_cursor_from: "partition-4:937000"
  source_cursor_to: "partition-4:938500"
code:
  repository: knowledge-pipeline
  commit: 6b1f2c...
environment:
  image_digest: sha256:ab12...
contracts:
  canonical_schema: canonical-doc-v3
  metadata_schema: metadata-v5
  acl_schema: acl-v2
components:
  parser: docling-2.x-config-7
  ocr: engine-x-model-y
  normalizer: ko-domain-v4
  chunker: semantic-v4
  embedder: example-embed-v3@revision
  sparse_analyzer: ko-domain-v5
outputs:
  raw_snapshot: raw-20260716.2
  canonical_snapshot: canonical-20260716.3
  chunk_snapshot: chunks-20260716.3
  index_generation: kb-prod-20260716.4
validation_report: reports/run_01j.json

Config object를 hash key로 쓸 때는 직렬화 순서가 달라 같은 config에 다른 digest가 생기지 않게 canonical serialization을 정해야 합니다. JSON에 대해서는 RFC 8785 JSON Canonicalization Scheme이 deterministic property sorting과 serialization 규칙을 정의합니다.

11. Provenance는 Citation보다 넓다

Citation은 answer가 참조한 page를 사용자에게 보여주는 기능입니다. Provenance는 그 page가 어떤 source revision과 처리 실행에서 왔는지까지 설명합니다.

answer claim
  → retrieved chunk ID
  → canonical element span
  → parser representation ID
  → raw content digest and source revision
  → source object and owning system

W3C PROV data model은 Entity, Activity, AgentwasGeneratedBy, used, wasDerivedFrom 관계로 provenance를 표현합니다. 내부 schema를 직접 설계하더라도 이 세 축을 유지하면 lineage query가 단순해집니다. PROV-O recommendation을 참고하세요.

Pipeline 실행 단위에는 OpenLineage core modelDataset, Job, Run과 extensible facet 구조도 좋은 출발점입니다. Parser version, schema, row count, data quality report를 run과 input/output dataset에 연결할 수 있습니다.

12. Candidate와 Active를 분리한다

Incremental upsert가 끝나는 순간 query가 새 index를 읽게 하면 다음 상태가 노출될 수 있습니다.

  • Dense index는 새 문서, sparse index는 이전 문서입니다.
  • 새 chunk가 추가됐지만 이전 revision chunk가 아직 남아 있습니다.
  • ACL field가 없는 일부 record가 먼저 공개됩니다.
  • 99%는 성공했지만 중요한 정책 문서 1%가 quarantine입니다.
  • 새 parser가 표의 열 순서를 뒤집었습니다.

따라서 write 대상과 read 대상을 분리합니다.

candidate generation 42
  → build all indexes
  → seal writes
  → validate
  → publish active pointer: 41 → 42
  → keep 41 for rollback window

Pointer swap은 한 transaction 또는 atomic alias update로 수행합니다. Query request는 시작 시 읽은 generation을 response가 끝날 때까지 유지해 한 답변 안에서 세대가 섞이지 않게 합니다.

13. Validation Gate를 층별로 둔다

Gate 1. Completeness

expected source revisions
  = indexed revisions
  + explicitly quarantined revisions
  + approved exclusions

조용히 사라진 record가 없어야 합니다. 전체 count만 같아도 안 됩니다. Stable ID set 차이를 비교합니다.

Gate 2. Structural Quality

  • Page coverage
  • Reading order violation
  • Empty text rate
  • OCR low-confidence page rate
  • Table row·column consistency
  • Citation span round-trip success

Gate 3. Security Parity

  • 모든 searchable chunk에 ACL reference가 있는가?
  • Source에서 revoke된 principal이 candidate에서도 제거됐는가?
  • Restricted corpus canary가 unauthorized query에서 0건인가?
  • ACL event에서 active 반영까지 SLO를 만족하는가?

Gate 4. Retrieval Quality

고정 gold query에서 Recall@k, MRR, nDCG, citation exact match를 이전 active와 비교합니다. 검색 평가 기초의 dataset과 metric contract를 재사용할 수 있습니다.

Gate 5. Freshness와 Operations

  • Event-to-active p95·p99
  • Backlog age와 poison message 수
  • Index storage·memory headroom
  • Build duration과 rollback 준비 상태

14. 실패한 문서를 숨기지 말고 Quarantine한다

“오류는 로그에 남기고 다음 파일로 진행”하면 corpus가 조용히 불완전해집니다. Quarantine record는 다음을 가져야 합니다.

source_object_id: obj_...
source_revision: rev-8
stage: NORMALIZE
error_class: TABLE_MODEL_TIMEOUT
retryable: true
attempts: 4
first_failed_at: 2026-07-16T01:10:00Z
last_failed_at: 2026-07-16T01:42:00Z
input_digest: sha256:...
component_revision: parser-config-7
owner: knowledge-platform
next_action: retry-with-fallback-ocr

Candidate release report에는 quarantine count만 아니라 어떤 business class가 빠졌는지 포함합니다. “전체의 0.1%”라도 긴급 대응 절차서 100%가 빠졌다면 배포하면 안 됩니다.

15. Backfill과 Replay를 일급 기능으로 만든다

다음 변화는 전체 또는 부분 재계산을 요구합니다.

변경다시 계산할 시작점
Parser bug fixRaw snapshot → canonical 이후
Metadata ontology 변경Canonical metadata 또는 enrichment 이후
Chunking 변경Canonical → chunk 이후
Embedding model 변경Chunk → dense index 이후
ANN parameter 변경Vector data → ANN build 이후
ACL schema 변경ACL normalization → 모든 serving index

모든 단계를 하나의 거대한 job으로 묶으면 작은 변경에도 원문부터 다시 fetch합니다. Immutable intermediate와 content-addressed cache가 있으면 영향받은 boundary부터 replay할 수 있습니다.

def materialize(document_version, contract):
    raw = cache.get_or_build(raw_key(document_version), fetch)
    canonical = cache.get_or_build(
        canonical_key(raw.digest, contract.parser),
        lambda: parse(raw, contract.parser),
    )
    chunks = cache.get_or_build(
        chunk_key(canonical.digest, contract.chunker),
        lambda: chunk(canonical, contract.chunker),
    )
    return index(chunks, contract.retrieval)

16. 가상 반도체 설비 문서로 본 Lifecycle

아래는 특정 회사의 실제 데이터나 운영 수치가 아닌 설계 설명용 가상 예시입니다.

Source:
  Etcher-X alarm manual revision 7
  effective: 2026-06-01
  ACL: process-engineering group

Change:
  revision 8 published at 10:00
  E204 recovery order changed
  vendor contractor access revoked at 10:02

Pipeline은 다음처럼 처리합니다.

  1. revision 8 UPSERTACL_CHANGED를 각각 받습니다.
  2. Revision 8 byte와 revision 7 byte를 모두 raw snapshot으로 보존합니다.
  3. Canonical parser가 표·경고 box·단계 번호를 구조화합니다.
  4. Revision 8 chunk를 만들고 revision 7 chunk를 superseded로 표시합니다.
  5. 새 ACL snapshot을 모든 derived chunk에 연결합니다.
  6. Candidate generation에서 E204 gold query와 unauthorized contractor query를 실행합니다.
  7. Citation이 revision 8의 정확한 page·span으로 round-trip되고 ACL query가 0건이면 공개합니다.
  8. Query plane pointer를 새 generation으로 바꿉니다.
  9. Revision 7은 audit·rollback retention 동안 남기되 active 검색에서는 제외합니다.

내용 변경과 권한 변경의 지연 요구가 다를 수 있습니다. 보안 revoke는 긴 batch release를 기다리지 않도록 deny overlay나 emergency tombstone 경로를 별도로 둘 수 있습니다. 이 주제는 5편 ACL 설계에서 다룹니다.

17. 최소 구현은 세 Store와 한 Pointer로 시작한다

초기 시스템에 거대한 platform이 필요한 것은 아닙니다.

1. Object store
   raw bytes + canonical JSON + chunk dataset + reports

2. Metadata database
   identity + state + event cursor + manifests + lineage

3. Search stores
   immutable sparse/vector generations

4. Active generation pointer
   tenant/corpus → approved generation

중요한 것은 제품 이름이 아니라 다음 invariant입니다.

  • Active가 가리키는 generation은 sealed이며 검증 report를 갖습니다.
  • 모든 searchable chunk는 source revision과 ACL로 역추적됩니다.
  • 같은 input과 contract는 같은 logical artifact key로 수렴합니다.
  • Delete와 revoke는 upsert와 같은 중요도로 처리됩니다.
  • 이전 active generation은 정의된 window 동안 rollback 가능합니다.

18. Pipeline SLO를 정의한다

Query latency만 측정하면 지식이 낡은 문제를 놓칩니다.

SLO예시 정의주의
Content freshnesssource event 후 p95 15분 내 activecorpus class별로 다르게
ACL revocationrevoke 후 p99 2분 내 검색 불가보안 SLO는 더 엄격하게
Completenessexpected revision coverage 99.99%quarantine를 분모에서 숨기지 않음
Citation traceabilityreturned chunk의 100% source span 복원sampling이 아니라 serving invariant 권장
Replayabilitymanifest로 candidate 재생 성공dependency artifact retention 필요
Rollback5분 내 이전 generation 전환old generation health 확인

수치는 예시값입니다. 데이터 민감도, 변경 빈도, source API limit, corpus 크기와 business impact에 맞춰 정합니다.

19. 처음 도입할 때의 순서

1단계. 현재 Index의 정체를 기록한다

지금 쓰는 source snapshot, parser, chunker, embedder, index config를 한 manifest로 묶습니다. 모르는 항목은 unknown으로 숨기지 말고 debt로 표시합니다.

2단계. Stable Source Identity와 Delete를 추가한다

Upsert만 있는 pipeline에 object ID, revision, tombstone을 넣습니다. 같은 문서를 재처리해도 중복 chunk가 늘지 않는지 확인합니다.

3단계. Candidate와 Active를 분리한다

Index alias 또는 generation pointer를 도입합니다. Query가 candidate를 직접 읽지 못하게 합니다.

4단계. Completeness와 ACL Gate부터 자동화한다

고급 semantic metric보다 누락과 정보 유출을 먼저 막습니다.

5단계. Replay와 Freshness SLO를 운영한다

Parser upgrade backfill과 source outage recovery를 실제로 연습합니다. Runbook이 있어도 replay가 처음이면 복구 계획이 아닙니다.

실전 체크리스트

  • Search index를 source of truth가 아닌 derived artifact로 정의했다.
  • Raw snapshot을 immutable하게 보존하고 content digest를 기록한다.
  • Canonical document가 page·bbox·table·source span을 보존한다.
  • Source object, business version, parser representation ID를 구분한다.
  • Upsert·delete·ACL·metadata change event를 모두 처리한다.
  • Initial snapshot과 change stream 사이에 빈 구간이 없다.
  • Event time·observed time·indexed time·active time을 구분한다.
  • Worker retry가 같은 logical artifact로 수렴한다.
  • Run manifest가 code·container·schema·parser·chunker·embedder revision을 고정한다.
  • 모든 chunk가 raw source revision까지 역추적된다.
  • Candidate generation은 query plane에서 보이지 않는다.
  • Completeness·structure·ACL·retrieval·freshness gate가 있다.
  • Quarantine가 report와 release decision에 포함된다.
  • Active pointer 전환이 atomic하고 이전 generation rollback이 가능하다.
  • Content freshness와 ACL revocation SLO를 따로 둔다.
  • Backfill·replay를 production과 같은 artifact contract로 실행한다.

스스로 확인하기

Q1. Vector DB가 document ID 기반 upsert를 지원하면 수명주기가 해결되는가?

아닙니다. Upsert는 write primitive일 뿐입니다. Source revision, parser representation, chunk identity, delete·ACL event, candidate validation과 atomic publish를 별도로 설계해야 합니다.

Q2. 원문 PDF를 object storage에 두면 provenance가 확보되는가?

원문 보존은 출발점입니다. Retrieved chunk에서 canonical span, raw digest, source revision으로 이어지는 lineage edge와 이를 만든 run manifest가 있어야 답변 근거를 재현할 수 있습니다.

Q3. 작은 변경은 바로 active index에 쓰는 편이 더 신선하지 않은가?

빠르지만 sparse·dense·ACL이 서로 다른 시점의 상태를 노출할 수 있습니다. 짧은 micro-batch generation이나 versioned record와 read snapshot을 사용해 신선도와 일관성을 함께 설계합니다. 긴급 revoke는 별도 deny overlay를 둘 수 있습니다.

Q4. 실패 문서가 0.1%면 성공한 99.9%를 공개해도 되는가?

비율만으로 결정하면 안 됩니다. 누락된 business class, 보안 민감도, 기존 active 대비 regression을 확인해야 합니다. 명시적 quarantine와 승인된 exclusion만 completeness 식에 포함합니다.

Q5. Exactly-once queue를 쓰면 idempotency key가 필요 없는가?

필요합니다. Source 재전송, operator replay, timeout 뒤 재시도, backfill과 concurrent worker는 queue delivery 보장 밖에서도 중복을 만듭니다. Artifact identity와 conditional commit으로 처리 결과가 수렴해야 합니다.

마무리

Enterprise RAG의 첫 설계 대상은 embedding model이 아니라 지식의 identity와 state입니다.

immutable evidence
  + canonical structure
  + stable identity and lineage
  + idempotent change processing
  + validated generation
  + atomic publication and rollback
  = operable RAG knowledge base

다음 글 문서 구조 추출: Layout·OCR·표·읽기 순서에서는 raw PDF·scan·Office 문서를 citation 가능한 canonical element로 바꾸는 방법과 parser 평가셋을 설계합니다.

참고문헌

검증 메모 — 표준과 공식 문서는 2026년 7월 16일 확인했습니다. 이 글의 반도체 설비 문서는 특정 회사 사례가 아닌 가상 예시이며 SLO 수치도 시작점을 설명하기 위한 예시값입니다. 실제 source API의 consistency, revision·ACL semantics, retention과 보안 요구를 먼저 문서화한 뒤 target corpus로 snapshot/stream gap, replay, unauthorized retrieval과 rollback을 검증해야 합니다.