Part 7. Context Engineering: RAG, Memory, 최신성, 멀티테넌시
LLM 품질은 모델보다 컨텍스트 경로에 민감하다. RAG, memory, 최신성, 테넌트 경계를 시스템 관점으로 설계하는 방법을 정리한다.
많은 팀이 LLM 품질 문제를 모델 선택으로 해결하려 한다. 하지만 운영에서 자주 보는 현실은 다르다. 같은 모델이라도 어떤 컨텍스트를, 어떤 순서로, 어떤 필터를 거쳐 넣는지에 따라 결과가 크게 달라진다. 즉 품질의 상당 부분은 "Model Engineering"보다 "Context Engineering"에서 결정된다.
문제 제기
실무에서 컨텍스트 설계가 약할 때 나타나는 패턴은 다음과 같다.
- 오래된 문서가 최신 정책보다 우선 검색되어 잘못된 답변을 만든다.
- 멀티테넌트 검색에서 필터 누락으로 타 고객 데이터가 노출된다.
- 대화 메모리가 계속 누적되어 토큰 비용이 증가하고 답변 초점이 흐려진다.
- retrieval top-k를 단순히 늘려 관련성은 낮고 노이즈만 많은 컨텍스트가 주입된다.
실전 예시 A: B2B 지원 포털
운영팀이 정책 문서를 매주 갱신하는데 인덱스 반영이 하루 지연되었다. 챗봇은 "구버전 환불 규정"을 근거로 답변했고, CS 정정 업무가 폭증했다. 모델 성능 문제가 아니라 최신성(freshness) 파이프라인 문제가 본질이었다.
실전 예시 B: 멀티테넌트 운영 어시스턴트
테넌트 필터를 retrieval 후(post-filter)에만 적용했더니, 검색 단계에서 이미 다른 테넌트 문서가 상위 후보에 섞였다. 후처리에서 일부 제거했지만 모델 프롬프트 조합 과정에서 누락이 발생해 데이터 혼선 사고로 이어졌다.
핵심 개념
Context Engineering은 단순 RAG 구현이 아니라 "지식 공급망" 설계다. 최소한 다음 네 질문에 답할 수 있어야 한다.
- 어떤 데이터를 컨텍스트로 허용할 것인가? (출처/권한/신뢰도)
- 어떤 데이터가 우선되는가? (최신성/도메인 가중치/정확도)
- 얼마나 넣을 것인가? (토큰 예산/요약/압축)
- 언제 폐기/갱신할 것인가? (TTL/재인덱싱/메모리 소거)
| 구성 요소 | 책임 | 대표 실패 | 필수 제어 |
|---|---|---|---|
| Retriever | 후보 문서 탐색 | 관련성 저하 | hybrid search, score threshold |
| Ranker | 문서 우선순위 결정 | 최신성 무시 | recency boost, policy weight |
| Context Builder | 프롬프트 조합 | 노이즈 주입 | dedup, chunk cap, compression |
| Memory Store | 세션 맥락 유지 | 맥락 오염 | TTL, scope(tenant/user/session) |
| Freshness Pipeline | 인덱스 갱신 | 구버전 답변 | CDC, reindex SLA |
실전 패턴
패턴 1: 최신성 가중치(Freshness-Aware Ranking)
정확한 답변을 위해 관련성만 보면 안 된다. 정책/가격/운영 절차처럼 시간 민감한 도메인은 최신성 점수를 결합해야 한다.
type Candidate = {
id: string;
semanticScore: number; // 0..1
lexicalScore: number; // 0..1
updatedAt: string;
sourceWeight: number; // 신뢰도 0..1
};
function recencyScore(updatedAt: string): number {
const ageHours = (Date.now() - new Date(updatedAt).getTime()) / 3_600_000;
if (ageHours <= 24) return 1.0;
if (ageHours <= 24 * 7) return 0.8;
if (ageHours <= 24 * 30) return 0.5;
return 0.2;
}
export function rank(candidates: Candidate[]) {
return [...candidates].sort((a, b) => {
const scoreA = 0.45 * a.semanticScore + 0.2 * a.lexicalScore + 0.25 * recencyScore(a.updatedAt) + 0.1 * a.sourceWeight;
const scoreB = 0.45 * b.semanticScore + 0.2 * b.lexicalScore + 0.25 * recencyScore(b.updatedAt) + 0.1 * b.sourceWeight;
return scoreB - scoreA;
});
}
운영 포인트:
- 도메인별(정책/기술문서/공지) 최신성 가중치를 다르게 둔다.
- 검색 결과에
updatedAt을 포함해 답변 근거와 함께 노출한다. - freshness 회귀(오래된 문서 인용률 증가)를 품질 경보로 운영한다.
패턴 2: 메모리 스코프 분리와 TTL
메모리는 많을수록 좋은 것이 아니다. 사용자/세션/태스크 스코프를 분리하지 않으면 오답과 비용이 동시에 늘어난다.
type MemoryRecord = {
key: string;
tenantId: string;
userId: string;
sessionId: string;
taskType: "support" | "analysis" | "admin";
content: string;
createdAt: string;
expiresAt: string;
};
export function memoryKey(input: {
tenantId: string;
userId: string;
sessionId: string;
taskType: string;
}) {
return `${input.tenantId}:${input.userId}:${input.sessionId}:${input.taskType}`;
}
export function shouldKeepMemory(taskType: string) {
if (taskType === "support") return { ttlMin: 30 };
if (taskType === "analysis") return { ttlMin: 180 };
return { ttlMin: 15 };
}
운영 포인트:
- 스코프를 넘어서는 메모리 공유는 기본 금지(default deny)로 시작한다.
- TTL 만료율과 재생성 비용을 같이 봐서 적정 보존 시간을 조정한다.
- 메모리 주입 전 안전 필터(민감정보, 지시문 오염)를 반드시 통과시킨다.
패턴 3: 멀티테넌트 Retrieval의 이중 경계
테넌트 경계는 retrieval 이전과 이후 모두 적용해야 한다.
{
"retrieval_policy": {
"tenant_filter_pre": true,
"tenant_filter_post": true,
"max_chunks": 12,
"min_score": 0.58,
"cross_tenant_allowed": false,
"cross_tenant_exception": []
},
"memory_policy": {
"scope": ["tenant", "user", "session"],
"default_ttl_min": 30,
"pii_redaction": "strict"
}
}
# 최신성 SLO 체크: 24시간 이내 갱신 문서 반영률
./ops/freshness/check-index-lag.sh \
--source policy_docs \
--target-index primary_vector \
--slo "p95<15m" \
--alert-channel platform-oncall
운영 포인트:
- pre-filter 누락은 설계 결함으로 분류하고 즉시 릴리즈 차단한다.
- 인덱스 지연(
index_lag)을 품질 SLO 일부로 포함한다. - 테넌트 경계 테스트를 통합 테스트가 아닌 계약 테스트로 고정한다.
실패 사례/안티패턴
장애 시나리오: "정책 변경 후 틀린 답변 급증"
상황:
- 환불 정책이 오전 10시에 변경됐지만 인덱스 재빌드가 16시에 완료됐다.
- 그 사이 챗봇은 구버전 정책을 인용했고, 잘못된 안내가 수백 건 발생했다.
- 시스템은 에러가 없어 장애로 인식하지 못했다.
탐지 절차:
stale_citation_ratio급등 감지- trace에서 retrieval 문서
updatedAt분포 분석 - source-of-truth DB와 인덱스 버전 차이 확인
완화 절차:
- 정책 도메인 질의는 임시로 DB 직접 조회 경로 우선 사용
- 오래된 문서 인용 시 답변 차단 + human-review 전환
- 해당 시간대 답변 이력 재검토 및 사용자 정정 안내
회복 절차:
- 정책 문서 도메인에 대해 near-real-time 인덱싱 도입
- freshness 가중치와
max_age정책 강제 - 배포 체크리스트에 "인덱스 반영 완료 확인" 단계 추가
대표 안티패턴
- RAG를 "벡터 검색 붙이기" 수준으로만 이해
- retrieval 품질과 최신성 지표를 운영 대시보드에서 분리
- 세션 메모리를 무기한 보존해 맥락 오염 유발
- 멀티테넌시에서 post-filter만으로 경계를 보장하려는 설계
체크리스트
- retrieval 설계에 관련성 + 최신성 + 신뢰도 가중치가 포함되는가?
- 테넌트 필터가 pre/post 단계 모두 적용되는가?
- 메모리 스코프(tenant/user/session/task)와 TTL이 명시되어 있는가?
- 인덱스 지연(
index_lag)을 품질 SLO로 운영하는가? - 오래된 인용(stale citation) 감지 경보가 있는가?
- context builder에서 chunk 수/중복/토큰 상한을 강제하는가?
- 컨텍스트 오염 시 human-review 우회 경로가 준비되어 있는가?
요약
Context Engineering의 핵심은 "많이 넣기"가 아니라 "정확히 넣기"다. RAG, 메모리, 최신성, 멀티테넌시를 각각 최적화하는 것이 아니라 하나의 데이터 공급망으로 설계해야 운영 품질이 안정된다. 모델이 같아도 컨텍스트 파이프라인이 다르면 결과는 완전히 달라진다.
다음 편 예고
다음 편에서는 Agent 아키텍처를 다룬다. Planner/Executor 분리, 상태 머신, 작업 큐, 툴 호출 가드레일을 결합해 "데모는 되는데 운영에서 망가지는" 에이전트 문제를 어떻게 해결하는지 설명한다. 특히 장기 작업과 실패 복구를 중심으로 실전 패턴을 정리한다.