Field Log · Entry
RAG 문서 ID·Versioning·Dedup: Rename·재처리·중복·삭제를 구분하는 법 (3/10)
오늘의 결론
- 파일 경로는 현재 위치이지 문서 identity가 아닙니다. Rename·move에도 유지되는
source_object_id와 업무상document_id를 별도로 둡니다.- Content hash는 “같은 bytes인가?”에는 강하지만 “같은 논리 문서인가?”에는 답하지 못합니다. 같은 내용의 두 정책 문서와 한 문서의 새 revision을 구분해야 합니다.
- Source revision, business version, ACL·metadata version, parser representation을 분리합니다. 원문이 그대로인데 parser만 바뀐 재처리를 새 업무 문서 revision으로 만들지 않습니다.
- Chunk ID를
document_id + chunk_17로 만들면 앞부분 한 줄 추가로 모든 ID가 바뀝니다. Canonical element span과 chunker revision에서 stable ID를 만듭니다.- Dedup은 hash가 내리는 자동 삭제 결정이 아닙니다. Exact·normalized·near·semantic duplicate를 다른 신호로 찾고, tenant·ACL·valid time·provenance를 보존하는 정책으로 최종 판정합니다.
앞 글에서는 PDF를 source 좌표를 가진 canonical elements로 변환했습니다. 이번 글은 그 element가 rename·재업로드·parser upgrade·중복·삭제를 거쳐도 같은 지식인지 판정할 identity와 version contract를 만듭니다.
locator changes, object remains
→ object accumulates business versions
→ one version can have multiple parser representations
→ one representation creates stable source-span chunks
→ fingerprints propose duplicate relations
→ policy decides link, suppress, supersede, or keep
→ tombstone propagates the same identities to every index
이 글에서 답하는 질문
- Path·URL·source ID·document ID·content hash는 어떻게 다른가?
- 원문 수정과 parser 재실행을 어떤 version으로 구분하는가?
- Exact duplicate와 near duplicate를 어떻게 탐지하고 판정하는가?
- Chunk 순번이 아닌 stable span ID는 어떻게 만드는가?
- 늦게 도착한 old event가 새 version을 덮어쓰지 않게 하는 방법은 무엇인가?
- Delete·rename·split·merge를 lineage와 tombstone으로 어떻게 표현하는가?
그림 1. Identity는 “무엇을 같은 것으로 볼 것인가”라는 계약이고, hash·MinHash·embedding은 그 판단에 쓰는 증거다. 하나의 fingerprint를 모든 층의 ID로 재사용하지 않는다.
1. 파일 경로를 ID로 쓰면 생기는 일
초기 RAG에서는 다음처럼 path를 primary key로 쓰기 쉽습니다.
doc_id = sha256(file_path.encode()).hexdigest()
/manuals/etcher/alarm.pdf를 /approved/etcher/alarm.pdf로 옮기면:
- 새 path가 새 document로 생성됩니다.
- 이전 path의 chunk가 남습니다.
- 같은 내용이 두 번 검색됩니다.
- Citation은 어느 path가 정본인지 모릅니다.
- 이전 path를 delete하면 새 path까지 지워야 하는지 판단하기 어렵습니다.
반대로 다른 파일이 이전 path를 재사용하면 과거 문서의 identity를 잘못 상속합니다. Path는 locator history로 저장하되 stable identity와 분리합니다.
2. Identity 계층을 먼저 정의한다
Locator
현재 접근 위치입니다.
file path · URL · bucket key · SharePoint path · database key
변경 가능하고 여러 locator가 같은 object를 가리킬 수 있습니다.
Source Object ID
원천 시스템 안의 논리 객체입니다. 가능하면 source가 제공하는 immutable object ID를 사용합니다.
source_system: sharepoint-prod
source_object_id: "item:01AX..."
current_locator: "/Approved/Etcher/Alarm.pdf"
locator_history:
- "/Draft/Etcher/Alarm.pdf"
- "/Approved/Etcher/Alarm.pdf"
Canonical Document ID
여러 source object가 같은 업무 문서를 표현할 수 있을 때 내부에서 부여하는 identity입니다.
vendor portal object
┐
├─ canonical document: Etcher-X Alarm Manual
internal DMS copy
┘
자동으로 합치기 전에 authoritative source, ownership, ACL과 retention 차이를 확인합니다.
Document Version ID
논리 문서의 특정 업무 상태입니다.
document_id: doc_01j...
document_version_id: docv_01j...
revision_label: "8"
valid_from: 2026-07-15T10:00:00Z
valid_to: null
parent_version_id: docv_01i...
source_revision: "etag:8f3a..."
Representation ID
같은 document version을 특정 parser와 schema로 변환한 결과입니다.
representation_id
= hash(document_version_id
+ raw_digest
+ parser_revision
+ canonical_schema_revision
+ normalization_revision)
Chunk ID
Representation 안의 stable source span과 chunker contract를 가리킵니다.
이 여섯 층을 하나의 doc_id 필드로 압축하지 않습니다.
3. Source Native ID와 내부 Surrogate ID
Source가 immutable ID를 제공하면 mapping의 anchor로 사용합니다. 그러나 source마다 형식과 보장 수준이 다릅니다.
source_identity_contract:
namespace: "sharepoint:tenant-a:site-b"
native_id: "item-1234"
stable_across_rename: true
stable_across_restore: false
may_be_reused_after_delete: false
source_documentation: "..."
내부 surrogate에는 random UUID나 time-ordered UUID를 쓸 수 있습니다. RFC 9562는 UUID format을 정의하고, UUIDv7에 Unix timestamp millisecond를 상위 48bit에 배치해 대체로 시간순 정렬되는 identifier를 제공합니다.
UUIDv7을 쓴다고 business identity가 자동으로 생기는 것은 아닙니다. UUID는 새 record key일 뿐이며 어떤 source object와 같은 문서인지 mapping rule이 별도로 필요합니다.
4. Content Address와 Business Identity는 다르다
Git은 content-addressable object store의 대표적인 예입니다. Git objects 문서는 content를 넣으면 hash 기반 key로 다시 찾는 구조를 설명합니다.
RAG artifact store에서도 유용합니다.
raw bytes → raw digest → immutable blob
canonical JSON → canonical digest → representation artifact
chunk text → chunk content digest → embedding cache
하지만 content address는 업무 identity를 대체하지 않습니다.
같은 Content, 다른 문서
- 같은 template로 시작한 부서 A·B 정책
- 같은 vendor manual을 서로 다른 tenant가 보유
- NDA와 ACL이 다른 동일 파일
- 같은 text지만 valid period가 다른 공지
다른 Content, 같은 문서
- Revision 7과 8
- Logo·footer만 바뀐 재배포
- OCR layer가 추가된 같은 scan
- 언어 번역본을 같은 document family로 묶는 경우
Hash는 content equality를 말하고, document model은 business continuity를 말합니다.
5. 여러 Digest의 역할을 분리한다
Raw Byte Digest
raw_digest = SHA-256(exact source bytes)
다운로드 무결성과 exact copy를 찾습니다. PDF metadata timestamp 한 byte만 바뀌어도 digest가 달라집니다.
RFC 9530 Digest Fields은 HTTP representation과 content에 digest field를 전달하는 방법을 정의하지만 특정 media type의 canonicalization은 제공하지 않는다고 명시합니다. 즉 byte digest와 semantic equality는 별개입니다.
Canonical Representation Digest
canonical_digest
= SHA-256(canonical_serialize(canonical_document))
Property order나 whitespace가 달라 digest가 바뀌지 않도록 deterministic serialization을 사용합니다. JSON이라면 RFC 8785 JCS를 참고할 수 있습니다.
Parser·normalization revision을 digest namespace에 넣습니다. 규칙이 바뀌면 같은 source도 새 representation입니다.
Normalized Text Digest
normalize:
Unicode normalization policy
line ending normalization
whitespace collapse
optional repeated header removal
explicit case policy
과도한 정규화는 다른 값을 같게 만듭니다.
"Part AB-01" vs "Part AB01"
"0.10 Torr" vs "010 Torr"
"OPEN" vs "Open"
Domain identifier, decimal point, unit과 case 의미를 보존합니다. Normalization policy revision을 반드시 기록합니다.
6. Version 축을 하나로 뭉치지 않는다
문서에는 여러 변화가 있습니다.
| Version 축 | 변화 예시 | 재계산 영향 |
|---|---|---|
| Source revision | 원문 byte 수정 | Raw 이후 전체 |
| Business revision | 승인 상태·효력 version | Active selection·temporal filter |
| ACL revision | 사용자·group 권한 변경 | Security index·cache |
| Metadata revision | 장비·공정·owner tag 수정 | Filter·ontology·possibly chunk context |
| Parser representation | Layout/OCR bug fix | Canonical 이후 |
| Chunk representation | Chunker 변경 | Chunk와 retrieval index |
| Embedding representation | Model revision 변경 | Dense index |
version=8 하나로는 parser-v7로 재처리한 결과와 업무 문서 revision 8을 구분하지 못합니다.
{
"document_version_id": "docv_8",
"source_revision": "etag-8",
"business_revision": "8",
"acl_revision": "acl-12",
"metadata_revision": "meta-5",
"representations": [
{"id": "repr-a", "parser": "parser-config-6"},
{"id": "repr-b", "parser": "parser-config-7"}
]
}
7. Version은 항상 선형 정수가 아니다
Source에 branch와 merge가 있을 수 있습니다.
revision 7
├─ vendor revision 8a
└─ internal annotated 7-local
└─ merged operational revision 9
Version edge를 명시합니다.
{
"version_id": "docv-9",
"parents": ["docv-8a", "docv-7-local"],
"relation": "merged_from",
"valid_from": "2026-07-16T00:00:00Z"
}
Search에서 “최신”은 가장 큰 정수보다 valid_from, approval state, audience와 lineage policy로 정합니다.
8. Event Version과 Business Version을 구분한다
Source event sequence는 처리 순서를 제어합니다.
source_partition: "manual-repo-4"
source_sequence: 938102
event_type: UPSERT
business_revision: "8"
Business label 8이 숫자 비교 가능한지는 source contract에 달렸습니다. A, B, 1.10, approved-final 같은 label을 정수로 해석하지 않습니다.
Index write에서는 monotonic source sequence나 source version을 사용해 늦은 event를 차단합니다. OpenSearch Index Document API는 external version이 현재 저장 version보다 큰 경우에만 index하는 external_gt 방식 등을 제공합니다. 제품 기능을 쓰지 않더라도 compare-and-set 계약은 필요합니다.
def apply_event(current, event):
if event.source_sequence <= current.last_source_sequence:
return "ignored_as_stale_or_duplicate"
write_new_state(event)
set_last_sequence(event.source_sequence)
return "applied"
Partition을 넘나드는 event에는 단일 sequence가 없을 수 있으므로 source의 ordering scope를 기록합니다.
9. Exact Dedup을 두 단계로 나눈다
Byte-exact Duplicate
Raw digest가 같습니다. Storage blob을 물리적으로 재사용할 수 있지만 논리 record와 ACL은 합치지 않을 수 있습니다.
Normalized-exact Duplicate
Raw byte는 다르지만 canonical text·structure digest가 같습니다.
same text, different PDF metadata
same content, different compression
same scan, OCR text layer newly embedded
정규화 규칙이 무엇을 지웠는지 audit할 수 있어야 합니다. Exact match라도 다음 조건이 다르면 별도 logical document로 유지합니다.
- Tenant
- Owner
- Legal basis
- ACL
- Valid time
- Source authority
- Retention policy
10. Near Duplicate는 후보를 찾는 단계다
문서가 일부만 다르면 exact hash가 모두 달라집니다.
Shingling
Text를 token 또는 character n-gram set으로 만듭니다.
text: "close isolation valve first"
3-token shingles:
close isolation valve
isolation valve first
두 set A, B의 Jaccard similarity:
$$ J(A,B)=\frac{|A\cap B|}{|A\cup B|} $$
MinHash
전체 shingle set을 매번 비교하지 않고 fixed-size signature로 Jaccard similarity를 근사합니다. Broder의 On the Resemblance and Containment of Documents는 resemblance와 containment를 set intersection과 sampling으로 다루는 기반을 제시합니다.
def near_duplicate_candidate(a, b):
return estimated_jaccard(a.minhash, b.minhash) >= THRESHOLD
Threshold는 language, boilerplate, document length에 따라 조정합니다. 짧은 안전 공지 두 개가 공통 template 때문에 높게 나올 수 있습니다.
11. Resemblance와 Containment를 같이 본다
큰 문서와 그 일부만 복사한 문서는 Jaccard가 낮을 수 있습니다.
A: 300-page manual
B: A의 5-page alarm appendix
Containment:
$$ C(B,A)=\frac{|B\cap A|}{|B|} $$
B의 대부분이 A 안에 있으면 containment는 높습니다. RAG corpus에는 다음 형태가 많습니다.
- Manual 전체와 분리 배포된 appendix
- Email thread와 마지막 reply
- Weekly report와 monthly compilation
- Policy 본문과 FAQ에 복사된 절
“비슷하니 하나 삭제”보다 relation을 표현합니다.
{
"from": "doc-small",
"to": "doc-large",
"relation": "mostly_contained_in",
"estimated_containment": 0.94,
"policy": "keep_both_but_diversify_retrieval"
}
12. Semantic Similarity로 자동 Dedup하지 않는다
Embedding similarity가 높은 문서는 같은 주제를 다룰 뿐 내용이 다를 수 있습니다.
"E204 발생 시 valve를 닫는다"
"E204 발생 시 valve를 열지 않는다"
문장 vector는 매우 가까울 수 있지만 조치가 반대입니다. Semantic similarity는 다음에 사용합니다.
- Duplicate review candidate 생성
- Version cluster 탐색
- Retrieval result diversification
- Conflicting claim detection
자동 삭제 gate로 쓰지 않습니다. Critical entity, number, negation, valid time과 source authority를 비교합니다.
13. Dedup Decision Matrix
| Raw digest | Canonical digest | Near score | Business identity | 권장 해석 |
|---|---|---|---|---|
| same | same | 1.0 | same | 같은 version 재전송 가능성 |
| same | same | 1.0 | different | 물리 blob 공유 가능, 논리 문서는 분리 |
| different | same | near 1.0 | same | Packaging/OCR layer 변화 가능 |
| different | different | high | same | 새 revision 후보 |
| different | different | high | different | Template·복사·관련 문서 후보 |
| different | different | low | same | 큰 개정, source mapping 오류 가능 |
결정 action은 다음 중 하나입니다.
KEEP_SEPARATE
LINK_AS_VERSION
LINK_AS_COPY
LINK_AS_TRANSLATION
MARK_CONTAINMENT
SUPPRESS_FROM_RETRIEVAL
SHARE_PHYSICAL_BLOB_ONLY
QUARANTINE_FOR_REVIEW
DELETE_DUPLICATE 하나로 모든 경우를 처리하지 않습니다.
14. Cross-tenant Dedup의 보안 함정
같은 bytes가 여러 tenant에 있어도 다음을 공유하면 안 될 수 있습니다.
- Logical document ID
- ACL record
- Embedding cache lookup timing
- Existence signal
- Retention·deletion state
Content-addressed physical storage를 공유하더라도 tenant-scoped reference와 encryption, delete reference counting, side-channel policy가 필요합니다.
physical blob sha256:X
├─ tenant A logical object + ACL A + retention A
└─ tenant B logical object + ACL B + retention B
Tenant A 삭제가 마지막 reference가 아니면 blob은 남을 수 있지만 A의 모든 serving pointer와 key access는 즉시 제거돼야 합니다.
15. Chunk 순번은 Stable ID가 아니다
다음 문서가 있습니다.
chunk_0: introduction
chunk_1: alarm E101
chunk_2: alarm E204
앞에 revision note 한 문단이 추가되면 모든 순번이 밀립니다.
chunk_0: revision note
chunk_1: introduction
chunk_2: alarm E101
chunk_3: alarm E204
이렇게 되면 변경되지 않은 chunk도 delete+insert로 처리되고:
- Embedding cache miss가 증가합니다.
- Feedback·click·annotation 연결이 끊깁니다.
- Citation ID가 모두 바뀝니다.
- Diff와 incremental indexing이 커집니다.
16. Stable Source Span에서 Chunk ID를 만든다
Canonical element가 stable ID를 갖는다고 가정합니다.
heading element: h_alarm_e204
paragraphs: p_condition, p_warning
table: t_recovery
Chunk identity:
chunk_identity_material
= representation_id
+ first_element_id
+ last_element_id
+ selected_subspan
+ chunker_revision
def chunk_id(representation_id, span, chunker_revision):
material = {
"representation": representation_id,
"start_element": span.start_element_id,
"start_offset": span.start_offset,
"end_element": span.end_element_id,
"end_offset": span.end_offset,
"chunker": chunker_revision,
}
return "chk_" + sha256(canonical_json(material)).hexdigest()[:32]
Chunker가 바뀌면 새 ID가 생기는 것이 맞습니다. 같은 chunker에서 앞의 무관한 section이 추가됐을 때는 기존 span ID가 유지될 수 있습니다.
17. Element ID도 순번만 쓰지 않는다
Element identity 전략:
Source-native Anchor
DOCX XML ID, HTML DOM ID처럼 source가 안정 anchor를 제공하면 사용합니다.
Structural Anchor
heading path + semantic role + local text fingerprint + neighborhood
Alignment across Versions
이전·새 representation의 element를 sequence alignment와 text similarity로 연결합니다.
{
"old_element": "elem-v7-142",
"new_element": "elem-v8-149",
"relation": "same_logical_element_modified",
"text_similarity": 0.93,
"structural_context_match": true
}
완벽한 stable ID가 불가능할 수 있으므로 confidence와 mapping provenance를 남깁니다.
18. Idempotency Key와 Artifact Key
Source event retry가 같은 결과로 수렴하려면 transition별 key가 필요합니다.
raw artifact key
= namespace + source_object_id + source_revision + raw_digest
canonical artifact key
= raw_digest + parser contract digest
chunk dataset key
= canonical_digest + chunker contract digest
embedding key
= chunk_content_digest + embedding model revision + normalization
Embedding cache에 chunk_id만 쓰면 chunk metadata가 바뀐 경우와 text가 같은 경우를 구분하기 어렵습니다. Model input을 정확히 반영한 digest를 사용합니다.
{
"text": "...",
"title_prefix": "Vacuum > Alarm E204",
"model": "embed-v3@revision",
"normalization": "l2",
"instruction": "Represent this document for retrieval"
}
이 전체 canonical input에서 cache key를 만듭니다.
19. Conditional Write로 경쟁 실행을 수렴시킨다
두 worker가 같은 document version을 동시에 처리할 수 있습니다.
worker A builds artifact X
worker B builds artifact X
→ both may compute
→ only one commits logical key X
→ the other verifies digest and reuses result
다른 bytes가 같은 logical key로 commit되면 nondeterminism 또는 key 설계 오류입니다. Last-writer-wins로 숨기지 말고 conflict로 격리합니다.
result = store.put_if_absent(key, artifact)
if result.existed and result.digest != artifact.digest:
raise NonDeterministicArtifactError(key)
20. Delete는 “이번 Scan에서 안 보임”과 다르다
Full scan에서 object가 보이지 않는 이유:
- 실제 삭제
- 권한 부족
- Pagination 누락
- API timeout
- Connector filter 변화
- Source 일시 장애
따라서 absence만으로 즉시 delete하지 않습니다. Source deletion event, authoritative snapshot completion과 previous set diff, 또는 명시적 reconciliation policy가 필요합니다.
not observed
→ suspect_missing
→ verify source and scan completeness
→ confirmed_deleted
→ emit tombstone
21. Tombstone Schema
{
"tombstone_id": "del_01j...",
"identity_scope": "source_object",
"source_object_id": "obj_...",
"document_version_id": null,
"source_sequence": 938301,
"deleted_at_source": "2026-07-16T02:10:00Z",
"observed_at": "2026-07-16T02:10:21Z",
"reason": "source_deleted",
"hard_delete_after": "2026-08-15T00:00:00Z",
"legal_hold": false,
"provenance": {"event_id": "evt_..."}
}
같은 tombstone identity를 다음에 전파합니다.
- Canonical active view
- Sparse index
- Vector index
- Graph nodes·edges
- Reranker cache
- Answer·prompt cache
- Citation lookup
- Export·analytics dataset
삭제와 retention은 9편에서 더 깊게 다룹니다.
22. Rename Event 처리
Source가 stable object ID와 rename event를 제공한다면:
object ID same
path old → path new
content revision same
→ update locator history
→ do not rebuild content artifacts
→ refresh path-based metadata only if used
Source가 stable ID를 제공하지 않으면 evidence를 조합합니다.
old disappeared + new appeared
+ raw digest same
+ close event time
+ same owner/ACL
+ source audit rename event if available
→ propose rename mapping
자동 merge의 confidence와 rollback path를 둡니다.
23. Split과 Merge를 Version Relation으로 표현한다
한 manual이 두 문서로 분리될 수 있습니다.
doc A revision 7
├─ doc B "Operations" revision 1
└─ doc C "Maintenance" revision 1
또는 두 정책이 하나로 합쳐질 수 있습니다.
{
"new_document_version": "docv-D-1",
"derived_from": ["docv-A-7", "docv-B-4"],
"relation": "merged_and_reorganized",
"mapping_artifact": "s3://lineage/maps/...json"
}
단순 duplicate suppression으로 처리하면 historical citation과 effective policy를 잃습니다.
24. Latest 선택은 Query Policy다
모든 query가 최신 version만 원하는 것은 아닙니다.
"현재 E204 대응 절차는?"
→ active approved version at query time
"2025년 사고 당시 유효한 절차는?"
→ version valid at incident time
"revision 7과 8의 차이는?"
→ both versions and lineage diff
Index record에 valid time과 transaction time을 둘 수 있습니다.
valid_from: 2026-07-15T10:00:00Z
valid_to: null
recorded_from: 2026-07-15T10:03:21Z
recorded_to: null
approval_state: approved
Freshness와 temporal retrieval은 6편에서 다룹니다.
25. 가상 반도체 Manual 사례
아래는 identity 설계를 설명하기 위한 가상 예시입니다.
10:00 revision 8 published at /Draft/Alarm.pdf
10:05 same object moved to /Approved/Alarm.pdf
10:07 another team uploads identical bytes to /Training/Alarm.pdf
10:10 parser-config-7 reprocesses approved object
10:12 delayed revision 7 event arrives
10:20 training copy gets separate ACL
권장 처리:
- 10:05 move는 source object ID가 같으므로 locator만 변경합니다.
- 10:07 copy는 raw digest가 같아도 별도 source object·ACL을 유지합니다.
- 두 object가 같은 canonical document를 표현하는지는 source authority policy로 연결합니다.
- 10:10 parser 재실행은 revision 8의 새 representation입니다.
- 10:12 event는 source sequence가 낮아 active state를 덮어쓰지 않습니다.
- Training copy는 중복 제거로 삭제하지 않고 retrieval diversification이나 authority ranking을 적용합니다.
26. Identity Registry 최소 Schema
source_object(
source_namespace,
source_object_id,
internal_object_id,
current_locator,
created_at,
deleted_at
)
document(
document_id,
document_family_id,
authoritative_source_policy,
owner
)
document_version(
document_version_id,
document_id,
source_revision,
business_revision,
raw_digest,
valid_from,
valid_to,
approval_state
)
representation(
representation_id,
document_version_id,
parser_contract_digest,
canonical_digest,
status
)
identity_relation(
from_id,
to_id,
relation_type,
confidence,
evidence_ref,
decided_by,
decided_at
)
Dedup decision을 record 덮어쓰기로 숨기지 않고 relation으로 보존합니다.
27. Identity Invariant Test
Pipeline test에 다음 property를 넣습니다.
rename invariant:
same source object + same revision + new locator
→ same document version and representation
retry invariant:
same event replay N times
→ one logical artifact set
parser upgrade invariant:
same document version + new parser
→ new representation, same business version
stale event invariant:
lower source sequence after higher sequence
→ active state unchanged
chunk locality invariant:
insert unrelated section before target
→ unchanged target element/chunk IDs preserved when alignable
delete invariant:
confirmed tombstone
→ no searchable child remains in active generation
28. Dedup Evaluation Set
Positive·negative pair를 실제 failure mode별로 만듭니다.
positive_relations:
- exact_reupload
- same_content_different_pdf_metadata
- revision_with_small_edit
- appendix_contained_in_manual
- renamed_object
negative_hard_pairs:
- same_template_different_alarm_code
- same_text_different_tenant_acl
- opposite_negation
- old_vs_current_validity
- translation_with_local_policy_difference
평가 지표:
- Candidate pair recall
- Review precision
- Wrong-merge rate
- Missed-duplicate rate
- Version-link accuracy
- ACL·tenant boundary violation 0건
- Retrieval duplicate occupancy@k
- Stable chunk ID reuse ratio across minor versions
Wrong merge는 missed duplicate보다 훨씬 위험할 수 있습니다. 비용을 비대칭으로 둡니다.
실전 체크리스트
- Path·URL을 locator로 보고 stable identity와 분리했다.
- Source native ID의 rename·restore·delete 보장을 문서화했다.
- Source object와 canonical business document를 구분한다.
- Document version과 parser representation을 분리한다.
- Source·business·ACL·metadata·parser·chunk·embedding version 축이 있다.
- Raw byte, canonical, normalized text digest의 목적이 다르다.
- Normalization rule과 canonical serialization revision을 기록한다.
- Exact duplicate라도 tenant·ACL·valid time·owner를 합치지 않는다.
- Near duplicate는 shingle·MinHash와 containment를 함께 검토한다.
- Embedding similarity를 자동 삭제 조건으로 쓰지 않는다.
- Dedup action과 evidence를 relation record로 보존한다.
- Chunk ID가 ordinal이 아니라 stable element span을 사용한다.
- Artifact·embedding cache key가 실제 model input contract를 포함한다.
- Conditional write가 nondeterministic output을 감지한다.
- External source sequence로 stale event overwrite를 막는다.
- Absence와 confirmed deletion을 구분한다.
- Tombstone이 sparse·vector·graph·cache·citation에 전파된다.
- Rename·split·merge·translation relation을 표현할 수 있다.
- Identity invariant와 hard-negative dedup pair를 자동 시험한다.
스스로 확인하기
Q1. SHA-256이 같으면 같은 document ID를 써도 되는가?
아닙니다. 같은 bytes라도 tenant, ACL, owner, valid time과 source authority가 다를 수 있습니다. Physical blob은 공유할 수 있어도 logical document identity와 policy record는 분리해야 합니다.
Q2. Source path가 안정적이라면 별도 object ID가 꼭 필요한가?
Path가 정말 immutable하고 재사용되지 않는다는 source contract가 있다면 key로 쓸 수 있습니다. 그러나 rename·move·restore가 있는 대부분의 repository에서는 locator와 identity를 분리하는 편이 안전합니다.
Q3. Parser가 바뀌면 document version을 올려야 하는가?
Business document version은 그대로 두고 새 representation을 만듭니다. 그래야 원문 revision 변경과 parser bug fix의 영향을 구분하고 두 parser 결과를 같은 source에 비교할 수 있습니다.
Q4. MinHash similarity가 0.95면 duplicate로 삭제해도 되는가?
안 됩니다. Threshold는 후보 생성용입니다. Business identity, critical entity·number·negation, containment, ACL, valid time과 authority를 확인해 relation과 action을 결정합니다.
Q5. Full scan에서 사라진 object는 바로 tombstone을 만들면 되는가?
안 됩니다. 권한·pagination·timeout·filter 변화로 보이지 않을 수 있습니다. Authoritative scan completion과 source delete evidence를 확인한 뒤 tombstone을 발행합니다.
마무리
RAG identity 설계의 핵심은 모든 것을 한 hash로 통일하는 것이 아니라 변화의 종류를 구분하는 것입니다.
locator tells where
object ID tells which source object
document ID tells which business document
version tells which state
representation tells which transformation
chunk span tells which evidence unit
fingerprints tell how similar
tombstone tells what must disappear
다음 글 RAG Metadata Schema·Ontology·Entity Resolution에서는 이 안정 ID에 장비·공정·문서 유형·시간·provenance를 연결하고, 자유 문자열을 통제된 vocabulary와 entity로 정규화하는 방법을 다룹니다.
참고문헌
- Davis, Peabody, Leach, RFC 9562: Universally Unique IDentifiers, IETF Proposed Standard, 2024.
- Rundgren et al., RFC 8785: JSON Canonicalization Scheme, 2020.
- Polli and Pardue, RFC 9530: Digest Fields, IETF, 2024.
- Chacon and Straub, Git Internals: Git Objects, accessed 2026-07-16.
- Broder, On the Resemblance and Containment of Documents, Compression and Complexity of Sequences, 1997.
- OpenSearch Project, Index Document API and External Versioning, accessed 2026-07-16.
- Apache Iceberg, Table Format Specification — Snapshots, Sequence Numbers, Schema IDs, accessed 2026-07-16.
검증 메모 — RFC, specification과 공식 문서는 2026년 7월 16일 확인했습니다. Hash·MinHash threshold와 source ID semantics는 구현·corpus에 따라 달라집니다. 본문의 반도체 manual timeline은 가상 예시이고, 자동 dedup·merge를 권장하는 수치가 아닙니다. 실제 source의 rename·restore·revision·delete 계약을 확인하고 tenant·ACL·valid-time hard negative를 포함한 identity 평가셋으로 wrong merge와 deletion propagation을 검증해야 합니다.