Field Log · Entry

RAG Freshness·Temporal Retrieval·CDC: 최신 문서를 정확히 검색하는 법 (6/10)

원천 변경 이벤트가 CDC와 watermark, version gate, parse·embed·index 단계를 지나 active generation에 반영되고 event time과 system time으로 freshness를 측정하는 RAG 시간축

오늘의 결론

  • “최신 RAG”는 크롤러를 자주 돌리는 기능이 아닙니다. 원천 변경이 언제 발생했고, 언제 관측·처리·색인·활성화됐는지 각 시간을 분리해 측정하는 운영 계약입니다.
  • 문서의 업무상 유효 시간과 시스템이 그 사실을 알게 된 시간은 다릅니다. 최신 승인 절차와 “지난달 당시 유효했던 절차”를 모두 답하려면 valid time과 system time을 분리합니다.
  • CDC는 변경을 빠르게 전달하지만 완전성을 자동 보장하지 않습니다. Initial snapshot, streaming change, idempotent replay와 주기적 reconciliation을 함께 둡니다.
  • Event는 늦게 오거나 순서가 뒤집힐 수 있습니다. Stable version, source sequence와 watermark로 old update가 새 문서를 덮어쓰지 못하게 합니다.
  • Content, ACL, deletion의 freshness 위험은 다릅니다. 특히 revoke와 tombstone은 일반 content backfill보다 우선 처리하고 serving·cache·citation까지 제거됐는지 검증합니다.

앞 글에서는 허용되지 않은 chunk가 retrieval 경계를 넘지 않게 했습니다. 그 설계는 ACL metadata가 제때 갱신된다는 조건 위에 있습니다. 이번 글은 “제때”를 timestamp와 SLO로 정의합니다.

source change at event time
  → observed by connector
  → captured as durable change event
  → parsed and embedded
  → written to candidate index
  → activated for queries
  → verified against source

원천 event time과 connector observed time, processed·indexed·active time을 분리하고 CDC, watermark, version gate, tombstone, reconciliation으로 최신 상태를 유지하는 RAG freshness pipeline

먼저 답하기: updated_at 하나면 왜 부족한가?

Document row에 updated_at=10:00이 있다고 합시다.

  • 원천 사용자가 10:00에 수정했는가?
  • Connector가 10:00에 발견했는가?
  • Parser가 10:00에 새 representation을 만들었는가?
  • Vector index에 10:00에 쓰였는가?
  • Query traffic이 보는 generation이 10:00에 바뀌었는가?
  • Timestamp가 source timezone인가 UTC인가?

이 질문에 답할 수 없다면 freshness lag의 원인과 책임 구간도 알 수 없습니다.

이 글의 대상과 학습 시간

  • 대상: 사내 RAG ingestion, stream processing, index 운영을 시작하는 초·중급 개발자
  • 선수 지식: document version, CDC, vector index의 기본 개념
  • 빠르게 읽기: 약 11분
  • 시간 모델까지 이해하기: 약 30분
  • replay·reconciliation 실습 포함: 약 90분

1. Freshness의 세 가지 질문을 분리한다

질문 A. Source가 바뀐 뒤 언제 검색 가능한가?

이것이 propagation freshness입니다.

propagation_lag = active_at - source_event_at

질문 B. 현재 검색 index가 source와 같은가?

이것이 state parity입니다. Lag가 짧아도 event 하나를 영구히 놓치면 parity는 깨집니다.

parity = canonical source state ↔ active search projection

질문 C. 업무상 어느 시점의 사실이 맞는가?

이것이 temporal validity입니다. 최신으로 index된 사실과 질문 시점에 유효한 사실은 다를 수 있습니다.

“지금 승인된 조치는?” → valid_at = now
“2026-06-01 당시 승인된 조치는?” → valid_at = 2026-06-01

세 문제는 서로 연결되지만 같은 metric이 아닙니다.

2. Pipeline time을 단계별로 저장한다

최소한 다음 시간을 구분합니다.

Field의미생성 주체
event_atSource에서 변경이 실제 발생Source system
observed_atConnector가 변경을 발견Connector
captured_atDurable event log에 기록CDC pipeline
processed_atParse·normalize 완료Ingestion worker
embedded_atEmbedding 생성 완료Embedding worker
indexed_atCandidate index write 성공Index writer
active_atQuery가 해당 generation을 보기 시작Release controller
verified_atSource와 projection parity 확인Reconciler

각 시간은 UTC timestamp와 precision을 명시합니다. Source가 날짜만 제공하면 자정으로 꾸며내지 말고 precision을 따로 둡니다.

{
  "event_at": "2026-07-16T01:00:00Z",
  "event_time_precision": "second",
  "observed_at": "2026-07-16T01:00:08Z",
  "captured_at": "2026-07-16T01:00:09Z",
  "processed_at": "2026-07-16T01:01:22Z",
  "embedded_at": "2026-07-16T01:01:31Z",
  "indexed_at": "2026-07-16T01:01:35Z",
  "active_at": "2026-07-16T01:02:00Z"
}

이제 구간별 lag를 계산할 수 있습니다.

detection lag = observed_at - event_at
queue lag = processed_at - captured_at
embedding lag = embedded_at - processed_at
index lag = indexed_at - embedded_at
release lag = active_at - indexed_at
end-to-end lag = active_at - event_at

3. Valid time과 system time을 분리한다

Valid time

업무 세계에서 사실이 유효한 기간입니다.

procedure v7 valid_from 2026-07-20
procedure v6 valid_to   2026-07-20

문서가 7월 16일 승인됐어도 7월 20일부터 적용될 수 있습니다.

System time

시스템이 그 사실을 언제 기록하고 알고 있었는지 나타냅니다.

recorded_from 2026-07-16T02:00Z
recorded_to   infinity

나중에 과거 유효 날짜가 잘못됐음을 발견하면 system history를 덮어쓰지 않습니다. 이전 assertion을 닫고 corrected assertion을 추가합니다.

Bitemporal record

{
  "document_id": "doc_sop_etch_204",
  "business_version": "v7",
  "valid_from": "2026-07-20T00:00:00+09:00",
  "valid_to": null,
  "recorded_from": "2026-07-16T02:00:00Z",
  "recorded_to": null,
  "source_revision": "rev-9811"
}

두 시간축으로 다음 질문을 구분할 수 있습니다.

  • 지금 유효한 절차는 무엇인가?
  • 6월 1일에 유효했던 절차는 무엇인가?
  • 6월 1일 당시 시스템이 알고 있던 내용은 무엇인가?
  • 지금 보정된 역사 기준으로 6월 1일의 사실은 무엇인가?

4. “최신”이라는 query intent를 명시한다

사용자가 “최신 E204 복구 절차”라고 물으면 다음 조건이 숨어 있습니다.

entity = equipment:EX01
alarm = alarm:E204
document_type = approved_procedure
approval_status = approved
valid_from ≤ query_time
valid_to > query_time OR null
not deleted
authorized for principal

단순히 updated_at desc로 정렬하면 draft, 미래 적용 version, 오래된 문서의 최근 metadata 수정이 위로 올라올 수 있습니다.

주요 temporal query mode

Mode의도Filter 기준
Current지금 유효valid interval contains now
As-of특정 시점 유효valid interval contains t
Known-at당시 시스템이 알던 것system interval contains t
Latest revision가장 최근 source revisionsource sequence
Recently changed최근 갱신event_at range
Scheduled미래 적용 예정valid_from > now

UI와 API에서 이 mode를 숨기지 않는 편이 좋습니다. “최신”이 모호하면 answer에 기준 시각과 version을 표시합니다.

5. Polling, webhook, CDC의 역할을 구분한다

Polling

every N minutes:
  list objects changed since cursor

장점:

  • 구현이 단순합니다.
  • Source가 API만 제공해도 쓸 수 있습니다.
  • Batch throttling이 쉽습니다.

한계:

  • Poll interval만큼 detection lag가 생깁니다.
  • updated_since semantics와 clock skew에 주의해야 합니다.
  • Delete를 목록에서 사라진 것으로만 표현하면 놓치기 쉽습니다.

Webhook

Source가 change notification을 밀어 줍니다. 빠르지만 중복·유실·순서 뒤집힘을 가정하고, notification을 source read의 trigger로 쓰는 편이 안전합니다.

Change Data Capture

Database log나 source change stream에서 insert·update·delete를 순서 정보와 함께 캡처합니다.

Debezium PostgreSQL connector는 PostgreSQL logical decoding을 이용해 row-level change event를 읽습니다. Debezium은 initial 또는 ad hoc snapshot과 streaming을 함께 지원하며, signaling 문서는 incremental snapshot 중 중복될 수 있는 event를 다루기 위해 watermarking mechanism을 사용한다고 설명합니다.

CDC의 장점:

  • 낮은 detection lag
  • Delete event를 명시적으로 전달
  • Source sequence·transaction context 활용
  • Replay 가능한 durable log 구성

CDC의 한계:

  • Connector configuration과 schema change 운영이 필요
  • Retention을 넘긴 offset 복구 문제
  • 외부 파일·SaaS API에는 직접 적용되지 않을 수 있음
  • Downstream parse·embed·index 완전성은 별도 책임

6. Snapshot과 stream을 함께 사용한다

처음부터 CDC만 읽으면 기존 수백만 문서는 들어오지 않습니다. 반대로 snapshot만 쓰면 snapshot 진행 중 변경을 놓칠 수 있습니다.

baseline snapshot
  + changes occurring during snapshot
  + continuous stream after snapshot
  = complete state, if sequence and dedup are correct

Initial snapshot

현재 source state를 읽어 baseline을 만듭니다.

Incremental snapshot

전체 pipeline을 멈추지 않고 범위를 작은 chunk로 나눠 backfill합니다. Debezium signaling은 runtime에 ad hoc incremental snapshot을 시작·중지·일시정지·재개할 수 있는 기능을 제공합니다. 구체 지원 범위는 connector version별로 확인해야 합니다.

Streaming changes

Snapshot과 겹치는 update가 중복으로 올 수 있습니다. Event ID, source sequence와 version gate로 idempotent하게 처리합니다.

7. Change event envelope을 표준화한다

Source마다 다른 payload를 그대로 downstream에 전달하지 않습니다.

{
  "event_id": "evt_01J2...",
  "source": "dms-prod",
  "tenant_id": "tenant_fab_a",
  "resource_id": "doc_sop_etch_204",
  "operation": "upsert",
  "source_revision": "rev-9811",
  "source_sequence": "lsn:0/16B6C50",
  "event_at": "2026-07-16T01:00:00Z",
  "observed_at": "2026-07-16T01:00:08Z",
  "payload_ref": "object://raw/sha256/...",
  "content_hash": "sha256:...",
  "acl_revision": "acl-771",
  "trace_id": "ingest-8821"
}

필수 성질:

  • Event ID로 중복 처리를 추적합니다.
  • Stable resource ID로 update와 delete가 같은 대상을 가리킵니다.
  • Source sequence로 순서를 비교합니다.
  • Payload는 immutable raw object를 참조합니다.
  • Content와 ACL revision을 분리합니다.
  • Event와 processing timestamp를 섞지 않습니다.

8. Exactly-once보다 idempotent effect를 설계한다

분산 pipeline에서 connector, queue, worker와 index writer 전체를 완전한 exactly-once로 묶기 어렵습니다. 실무적으로 중요한 것은 같은 event를 여러 번 처리해도 최종 상태가 같아지는 것입니다.

def apply_event(event, state):
    current = state.get(event.resource_id)

    if current and event.source_sequence <= current.source_sequence:
        return "ignored_old_or_duplicate"

    representation = build_representation(event.payload_ref)
    state.compare_and_swap(
        key=event.resource_id,
        expected_sequence=current.source_sequence if current else None,
        new_value=representation.with_sequence(event.source_sequence),
    )
    return "applied"

실제 sequence가 lexicographic 비교 가능한지는 source contract에 따라 다릅니다. Connector가 제공하는 offset 구조를 임의 문자열로 단순화하지 않습니다.

Idempotency key의 범위

(tenant_id, resource_id, source_revision, pipeline_revision)

Parser나 embedding model이 바뀌면 같은 source revision이라도 새 representation을 만들어야 하므로 pipeline_revision을 포함합니다.

9. Out-of-order event를 version gate에서 막는다

예를 들어 update v8이 먼저 처리되고 오래 걸린 v7 embedding이 나중에 끝날 수 있습니다.

v7 parse ───────────── embed complete at 10:08
v8 parse ── embed complete at 10:05

Completion time만 보면 v7이 마지막 write가 되어 최신 v8을 덮어씁니다.

해결:

  1. 모든 artifact에 source revision·sequence를 넣습니다.
  2. Index write 전에 canonical active revision과 비교합니다.
  3. Old revision은 artifact history에 보존할 수 있지만 active pointer를 바꾸지 않습니다.
  4. Release controller는 generation manifest의 source coverage를 검증합니다.
{
  "resource_id": "doc_sop_etch_204",
  "source_revision": "v8",
  "parser_revision": "parser-3.2",
  "embedding_revision": "embed-ko-7",
  "index_generation": "kb-2026-07-16.4",
  "status": "active"
}

10. Watermark는 “모든 event 도착”을 뜻하지 않는다

Stream partition마다 event time이 다르고 network·source delay로 늦은 event가 옵니다. Watermark는 보통 이 시각 이전 event가 더 올 가능성이 낮다고 시스템이 판단하는 진행 신호입니다.

Apache Flink 문서는 event time과 processing time을 구분하고, watermark를 event-time clock의 진행을 나타내는 장치로 설명합니다. Source partition 중 idle partition이 전체 watermark를 멈추게 할 수 있어 idleness 처리도 필요합니다.

Watermark를 쓰는 곳

  • Time window 단위 completeness 추정
  • Snapshot과 stream overlap 정리
  • Late event 관찰
  • Batch release cutoff
  • Reconciliation 범위 결정

Watermark가 보장하지 않는 것

  • Source가 event를 생성하지 않은 bug
  • Connector offset 유실
  • 무한히 늦은 event가 절대 오지 않음
  • Downstream index write 성공

따라서 allowed lateness와 late-event handling을 정합니다.

if event_time ≥ watermark:
  normal path
elif within allowed_lateness:
  correction path + metric
else:
  quarantine + reconcile + alert

11. Delete는 null update가 아니라 tombstone이다

Source에서 문서가 삭제됐는데 index에 남으면 RAG는 존재하지 않는 정보를 계속 인용합니다. 권한 회수와 개인정보 삭제에서는 더 심각합니다.

Delete event는 명시적으로 표현합니다.

{
  "operation": "delete",
  "resource_id": "doc_sop_etch_204",
  "source_revision": "rev-9812",
  "event_at": "2026-07-16T01:30:00Z",
  "reason": "source_deleted",
  "tombstone_id": "del-551",
  "retention_class": "standard"
}

Tombstone이 거쳐야 할 곳

  1. Canonical state에서 inactive 표시
  2. Chunk·embedding projection 제거
  3. Sparse·vector index에서 숨김 또는 삭제
  4. Retrieval·rerank·answer cache 무효화
  5. Citation fetch deny
  6. Evaluation·export의 retention policy 적용
  7. Backup·snapshot 만료 추적
  8. Deletion receipt 기록

Delete event가 update보다 먼저 도착했다가 늦은 update가 문서를 부활시키지 않도록 sequence gate를 적용합니다.

12. Soft delete와 hard delete를 구분한다

Soft delete

Serving에서 즉시 제외하되 복구와 audit를 위해 tombstone과 artifact를 보존합니다.

Hard delete

Policy에 따라 raw, canonical, derived artifact와 backup lifecycle에서 물리 제거합니다.

RAG freshness 관점의 첫 목표는 serving visibility가 즉시 0이 되는 것입니다. 물리 삭제의 세부 governance는 79번 글에서 다룹니다.

13. Content, ACL, deletion lane을 분리한다

모든 event를 한 queue에 넣으면 대량 embedding backfill이 revoke를 막을 수 있습니다.

priority 0: tenant disable · emergency deny · deletion
priority 1: ACL revoke
priority 2: ACL grant
priority 3: approved content update
priority 4: normal content update
priority 5: parser / embedding backfill

이 우선순위는 예시입니다. 조직 risk model과 source semantics에 맞춰 조정합니다.

Worker pool, retry budget와 dead-letter queue도 lane별로 분리합니다. Content parse 실패가 ACL projection update를 막지 않도록 permission-only update 경로를 둡니다.

14. Partial update의 atomic visibility를 설계한다

문서 하나가 200개 chunk로 바뀔 때 1번부터 순차 update하면 query 중 신·구 chunk가 섞일 수 있습니다.

전략 A. Generation pointer

write all chunks to generation g42
  → validate count and hashes
  → atomically set document.active_generation = g42
  → retire g41

전략 B. Index alias swap

전체 index release라면 candidate index를 완성한 뒤 alias를 바꿉니다.

전략 C. Version filter

Chunk에 document version을 두고 active version manifest와 일치하는 row만 검색합니다.

어떤 전략이든 query가 mixed representation을 보지 않는다는 invariant를 테스트합니다.

15. Snapshot table은 재현 가능한 기준점을 준다

Apache Iceberg spec은 table state를 snapshot으로 추적하고 snapshot sequence number와 timestamp 정보를 둡니다. 이러한 snapshot 모델은 RAG canonical dataset에도 유용합니다.

knowledge snapshot manifest
  snapshot_id
  parent_snapshot_id
  committed_at
  source_high_watermarks
  document_count
  chunk_count
  parser_revision
  embedding_revision
  ACL revision range

장점:

  • 어느 source cutoff로 index를 만들었는지 설명 가능
  • 재현 가능한 evaluation
  • Candidate generation 비교
  • Rollback 기준점
  • Incremental diff 계산

Vector index만 남기지 말고 그 index를 만든 canonical snapshot manifest를 보존합니다.

16. Reconciliation으로 event 누락을 찾는다

Event pipeline이 정상이어도 주기적으로 source와 비교합니다.

Level 1. Inventory parity

source resource IDs ↔ canonical active resource IDs
  • Missing in RAG
  • Extra in RAG
  • Unexpected resurrection

Level 2. Version parity

source revision / ETag / hash ↔ canonical source revision

Level 3. Projection parity

  • Expected chunk count
  • Chunk content hash
  • ACL projection hash
  • Parser·embedding revision
  • Active index generation

Level 4. Query probe

Known marker와 temporal query로 serving 결과까지 확인합니다.

Reconciliation 결과는 자동 repair event로 바꿀 수 있지만, 삭제·ACL mismatch는 먼저 deny하고 조사하는 것이 안전합니다.

17. Freshness SLO를 event class별로 정의한다

하나의 평균 지연으로 운영하지 않습니다.

Event class예시 SLO측정 시작측정 끝
Approved procedure updatep99 10분source eventquery active
Normal wiki editp99 30분source eventquery active
ACL grantp99 10분source ACL eventauthorized query visible
ACL revokep99 60초source ACL eventall probes denied
Deletep99 60초source deleteserving·citation hidden
Full reconciliation24시간scan startparity report complete

모두 [예시값]입니다. 중요한 것은 event class와 관측 지점을 명시하는 것입니다.

SLI 계산 예시

fresh_within_slo = count(active_at - event_at ≤ target) / eligible_events

Eligibility도 정해야 합니다.

  • Source timestamp가 없는 event는 별도 bucket
  • Manual review 대기 시간 포함 여부
  • Provider outage exclusion 여부
  • 미래 valid_from 문서는 active와 valid를 구분

18. Average 대신 lag distribution과 age를 본다

추천 dashboard:

  • End-to-end lag p50·p95·p99 by source/event class
  • Oldest unprocessed event age
  • Queue depth와 arrival/service rate
  • Watermark lag by partition
  • Late event count
  • Dead-letter age
  • Source vs index parity rate
  • Tombstone visibility lag
  • Resources by ACL snapshot age
  • Active generation source cutoff

평균은 하나의 stuck partition을 숨깁니다. oldest age와 source별 p99가 중요합니다.

19. Backpressure를 freshness budget으로 제어한다

Arrival rate가 worker 처리율보다 높으면 queue가 계속 자랍니다.

if arrival_rate > service_rate:
  backlog_age ↑
  freshness SLO burn ↑

대응 순서:

  1. Revoke·delete lane capacity를 보호합니다.
  2. 불필요한 unchanged content를 hash로 제거합니다.
  3. Parser·embedding batch와 concurrency를 조정합니다.
  4. Low-priority backfill을 일시정지합니다.
  5. Source별 circuit breaker와 retry jitter를 둡니다.
  6. SLO burn alert로 scale-out합니다.

Queue depth만 보고 autoscale하면 큰 payload와 작은 ACL event의 cost 차이를 놓칩니다. Processing time과 token·page·byte work unit도 함께 봅니다.

20. Retry와 dead letter를 설계한다

Retryable

  • Temporary source timeout
  • Rate limit
  • Embedding service 5xx
  • Index write timeout

Non-retryable until fixed

  • Schema contract violation
  • Unsupported file encryption
  • Missing tenant identity
  • Invalid ACL semantics
  • Content too large without partition rule

모든 실패를 무한 retry하면 poison event가 lane을 막습니다.

{
  "event_id": "evt_01J2...",
  "stage": "parse",
  "error_class": "unsupported_encryption",
  "attempts": 3,
  "first_failed_at": "2026-07-16T01:02:00Z",
  "last_failed_at": "2026-07-16T01:07:00Z",
  "next_action": "manual_source_owner",
  "source_sequence": "rev-9811"
}

Dead-letter event도 source가 더 새 version을 낸 경우 obsolete로 닫아야 합니다.

21. Temporal retrieval ranking을 설계한다

Temporal filter만으로 충분하지 않은 query도 있습니다.

Current authoritative answer

승인 상태와 valid interval을 hard filter하고 semantic relevance로 rank합니다.

Time decay를 soft score로 더할 수 있습니다.

score = semantic_score + λ × recency_decay(event_at, now)

λ는 dataset에서 튜닝해야 하는 예시값입니다. Recency가 authority를 이기지 않게 합니다.

Historical comparison

두 valid interval의 문서를 각각 검색해 변경점을 비교합니다. LLM이 최신 문서만 보고 과거 상태를 추측하게 하지 않습니다.

Event sequence query

Incident, alarm, maintenance event는 interval과 causal link가 중요합니다. Chunk text 검색 후 temporal join을 수행하거나 structured event store와 결합합니다.

22. Answer에 freshness evidence를 표시한다

사용자는 “최신”을 시스템처럼 해석하지 않습니다. Answer와 citation에 다음을 표시할 수 있습니다.

기준 시각: 2026-07-16 10:00 KST
문서 버전: SOP-ETCH-E204 v7
업무 유효 시작: 2026-07-15
원천 마지막 변경: 2026-07-15 18:42 KST
검색 반영 확인: 2026-07-15 18:45 KST
상태: 승인됨

Index lag가 SLO를 넘었다면 “최신”이라고 단정하지 않고 stale warning 또는 answer abstention을 선택합니다.

23. 가상 반도체 RAG 예시

다음은 설명을 위한 가상 예시입니다.

사건

  1. 09:00 — E204 조치 SOP v6가 유효
  2. 09:02 — v7 승인, 09:30부터 유효하도록 등록
  3. 09:03 — CDC가 v7 event 포착
  4. 09:04 — v7 parse·embed 완료
  5. 09:05 — v7 candidate index write
  6. 09:07 — 과거 v6 재처리 task가 늦게 완료
  7. 09:30 — v7 valid interval 시작

잘못된 구현

  • Last-write-wins로 09:07 v6가 active를 덮어씀
  • updated_at desc 때문에 09:05부터 미래 유효 v7을 현재 절차로 답함

개선 구현

  • Source sequence gate가 늦은 v6 write를 active에서 거부
  • v7은 09:05부터 searchable하지만 scheduled 상태
  • Current query는 09:30 전 v6, 이후 v7을 반환
  • Answer에 기준 시각·version·valid_from을 표시

평가 query

09:20 현재 조치 → v6
09:40 현재 조치 → v7
09:40에 “09:20 당시 조치” → v6
v7 승인 이력 → v7 scheduled/approved evidence

24. 구현 순서

1단계. Time dictionary

Source·connector·pipeline·business time field의 의미, timezone과 precision을 문서화합니다.

2단계. Event envelope

Stable resource ID, operation, source revision·sequence와 모든 stage timestamp를 표준화합니다.

3단계. Snapshot + stream

Baseline snapshot, overlap dedup과 continuous change path를 만듭니다.

4단계. Idempotent version gate

Duplicate·out-of-order event가 active state를 퇴행시키지 못하게 합니다.

5단계. Priority lanes

Content, ACL, deletion과 backfill을 위험에 따라 분리합니다.

6단계. Atomic visibility

Document generation 또는 index alias로 mixed version을 막습니다.

7단계. Temporal query

Current, as-of, known-at와 scheduled mode를 구현하고 gold query를 만듭니다.

8단계. Reconciliation과 SLO

Source parity, lag distribution, tombstone probe와 stale answer behavior를 release gate에 넣습니다.

실전 체크리스트

  • Event, observed, captured, processed, embedded, indexed, active time을 분리한다.
  • Timestamp timezone과 precision을 보존한다.
  • End-to-end뿐 아니라 stage별 lag를 측정한다.
  • Valid time과 system time이 구분돼 있다.
  • “최신” query가 approval·valid interval·delete·ACL 조건을 포함한다.
  • Polling cursor에 overlap과 dedup이 있다.
  • Webhook 중복·유실·순서 뒤집힘을 가정한다.
  • CDC에 baseline 또는 incremental snapshot 전략이 있다.
  • Event envelope에 stable resource ID와 source sequence가 있다.
  • Duplicate processing이 idempotent하다.
  • 늦은 old revision이 최신 active state를 덮어쓰지 못한다.
  • Watermark와 allowed lateness 의미가 명확하다.
  • Idle partition과 late event를 관찰한다.
  • Delete가 명시적 tombstone event다.
  • Tombstone이 index, cache와 citation까지 전파된다.
  • ACL revoke·delete가 backfill보다 우선 처리된다.
  • Document multi-chunk update가 atomic하게 보인다.
  • Active index가 canonical snapshot manifest를 참조한다.
  • Event 누락을 찾는 source reconciliation이 있다.
  • Freshness SLO가 event class별로 다르다.
  • Average 외에 p99, oldest age와 parity를 본다.
  • Authorization·delete dependency 실패는 fail closed다.
  • Answer에 version·valid time·기준 시각을 표시할 수 있다.

스스로 확인하기

Q1. 1분 polling이면 freshness SLO도 1분인가?

아닙니다. Detection 이후 download, parse, embed, index와 activation 시간이 더해집니다. Source API가 update를 늦게 노출하거나 queue가 밀리는 경우도 포함해야 합니다.

Q2. CDC를 쓰면 reconciliation은 필요 없는가?

아닙니다. Offset 손실, connector bug, schema 변화와 downstream 실패가 있습니다. CDC는 낮은 지연을, reconciliation은 장기 완전성을 담당합니다.

Q3. Event time이 없으면 observed_at을 대신 써도 되는가?

임의 대체하지 말고 event time unknown과 observed time을 구분합니다. 그렇지 않으면 detection lag를 0으로 오해합니다.

Q4. 미래 적용 문서는 index에 넣지 말아야 하는가?

보안 정책이 허용하면 scheduled 문서로 검색 가능하게 두되 current query에서는 valid interval로 제외할 수 있습니다. 업무 요구와 조기 공개 권한을 분리합니다.

Q5. Delete는 index에서 바로 지우면 끝나는가?

아닙니다. Cache, citation, raw·canonical artifact, evaluation export와 backup retention이 남습니다. Serving deny와 물리 삭제 완료를 별도 상태로 추적합니다.

Q6. 늦은 event는 모두 버려야 하는가?

아닙니다. 현재 active state를 퇴행시키지 않되 history correction, temporal view와 audit에는 필요할 수 있습니다. Version과 valid/system time에 따라 correction path로 보냅니다.

마무리

Freshness는 하나의 날짜 field가 아니라 source에서 사용자 answer까지 이어지는 관측 가능한 시간 계약입니다.

explicit time semantics
  + snapshot and change stream
  + idempotent sequence gate
  + watermark and late-event policy
  + tombstone and priority revoke
  + temporal query and reconciliation
  = trustworthy current and historical RAG

다음 글 Embedding·Index Migration과 Dual Read에서는 model·dimension·chunking·distance metric을 바꾸면서도 서비스 중단 없이 backfill, shadow read, alias cutover와 rollback을 수행하는 방법을 다룹니다.

참고문헌

검증 메모 — 공식 문서는 2026년 7월 16일 확인했습니다. Debezium 기능과 offset·snapshot semantics는 connector와 version별로 다르며, Flink watermark도 실제 source partition 설정에 따라 동작이 달라집니다. 본문의 시간, queue priority와 SLO는 가상 예시입니다. 실제 source clock, revision contract, delete semantics와 index consistency를 확인하고 duplicate·out-of-order·late event, connector restart, offset loss, partial write와 stale cache를 fault injection으로 검증해야 합니다.