AI 채팅 서비스는 대화를 어떻게 기억할까? - 컨텍스트 관리의 모든 것
ChatGPT나 Claude 같은 AI 채팅 서비스를 쓰다 보면 궁금해진다. "이전 대화를 어떻게 기억하지?" "HTML 파일을 계속 수정하는데, 매번 전체 파일을 다시 보내나?"
프론트엔드에서 채팅을 하면 서버가 LLM API를 호출하는 구조는 알겠는데, 대화 내역과 컨텍스트를 어떻게 관리하는지 는 잘 모른다. 이 글에서는 AI 채팅 서비스의 내부 동작 원리를 개념 중심으로 정리했다.
기본 아키텍처
AI 채팅 서비스의 기본 구조는 간단하다.
[클라이언트] <--HTTP--> [서버] <--API--> [LLM (Claude/GPT)]
↓ ↓
세션 ID 대화 내역 DB
(세션별 저장)
흐름:
- 사용자가 메시지 입력
- 클라이언트가 서버에 전송 (세션 ID 포함)
- 서버가 해당 세션의 대화 내역 조회
- 대화 내역 + 새 메시지를 LLM API로 전송
- LLM 응답을 DB에 저장
- 클라이언트에 응답 반환
핵심은 서버가 대화 내역을 관리 한다는 점이다.
대화 내역은 어디에 저장되나
방법 1: 서버에서 세션별 관리 (가장 흔함)
클라이언트가 서버에 보내는 것:
- 세션 ID
- 새 메시지만
서버가 하는 일:
- 세션 ID로 기존 대화 조회
- 새 메시지 추가
- 전체 대화 내역을 LLM에 전송
- 응답 받아서 저장
- 클라이언트에 응답만 반환
DB 구조 (개념):
세션 테이블:
- 세션 ID
- 사용자 ID
- 생성 시간
메시지 테이블:
- 메시지 ID
- 세션 ID
- 역할 (user / assistant)
- 내용
- 타임스탬프
장점:
- 서버가 모든 걸 관리하니 안전함
- 클라이언트는 가벼움
- 여러 기기에서 동일한 대화 이어가기 가능
단점:
- 서버 부담
- DB 용량 증가
방법 2: 클라이언트가 전체 히스토리 전송
클라이언트가 매번 전체 대화 내역 전송:
- 이전 대화 전부 + 새 메시지
장점:
- 서버가 상태를 저장하지 않아도 됨 (Stateless)
- 서버 구조 단순
단점:
- 네트워크 트래픽 증가
- 보안 취약 (대화 내역 노출)
- 여러 기기 동기화 어려움
→ 실무에서는 거의 안 씀
컨텍스트 윈도우 문제
문제: 토큰 제한
LLM은 한 번에 처리할 수 있는 텍스트 양에 제한이 있다.
- Claude Sonnet 4: 200,000 토큰
- GPT-4: 128,000 토큰
대화가 길어지면?
User: 메시지 1 (100 토큰)
AI: 응답 1 (200 토큰)
User: 메시지 2 (100 토큰)
AI: 응답 2 (200 토큰)
...
User: 메시지 100
→ 총 30,000 토큰 초과!
모든 대화를 LLM에 보낼 수 없다.
해결 방법들
1. 슬라이딩 윈도우 (가장 단순)
최근 N개 메시지만 유지
대화 내역: 100개 메시지
LLM에 전송: 최근 20개만
장점:
- 구현 간단
- 예측 가능한 토큰 사용
단점:
- 오래된 정보 잊어버림
- "아까 말한 그거..."가 안 통함
2. 토큰 기반 트리밍
토큰 수를 계산해서 제한
최대 토큰: 100,000
현재 토큰: 0
역순으로 메시지 추가:
- 최신 메시지: 200 토큰 → 누적 200
- 그 전 메시지: 150 토큰 → 누적 350
- ...
- 제한 초과 시 중단
장점:
- 토큰 효율적 사용
- 최신 정보 최대한 포함
단점:
- 토큰 계산 오버헤드
- 여전히 오래된 정보 손실
3. 요약 기법 (Summarization)
오래된 대화를 요약해서 압축
초기 10개 메시지:
User: "프로젝트 시작했어"
AI: "축하합니다!"
User: "React 쓸 거야"
AI: "좋은 선택입니다"
...
요약:
"사용자는 React로 프로젝트를 시작했습니다."
프로세스:
- 오래된 메시지를 별도 LLM 호출로 요약
- 요약본을 시스템 프롬프트에 추가
- 최근 메시지는 그대로 유지
장점:
- 오래된 정보도 보존
- 토큰 효율적
단점:
- 추가 LLM 호출 비용
- 요약 과정에서 정보 손실 가능
4. 벡터 DB + RAG (고급)
중요한 정보를 벡터 DB에 저장하고 필요할 때 검색
대화 저장:
- 모든 메시지를 벡터로 변환
- 벡터 DB에 저장 (Pinecone, Weaviate 등)
새 질문이 오면:
- 질문을 벡터로 변환
- 관련 있는 과거 메시지 검색 (Top 5)
- 검색된 메시지 + 최근 대화를 LLM에 전송
장점:
- 관련 있는 정보만 선택적으로 사용
- 아주 긴 대화도 처리 가능
단점:
- 구현 복잡
- 벡터 DB 인프라 필요
- 비용 증가
실무에서는?
단계별 진화:
- 초기 (MVP): 슬라이딩 윈도우 (최근 20개)
- 성장기: 토큰 기반 트리밍
- 성숙기: 요약 + RAG 조합
대부분은 토큰 기반 트리밍 으로 충분하다.
파일 반복 수정 시 상태 관리
문제 상황
HTML 파일을 반복적으로 수정하는 경우:
User: "버튼 색상을 파란색으로 바꿔줘"
AI: [전체 HTML 읽음 (10,000줄) → edit → 수정된 HTML 반환]
User: "이제 버튼 크기를 키워줘"
AI: [수정된 HTML 읽음 → edit → 다시 수정]
User: "폰트도 바꿔줘"
AI: [또 수정...]
문제:
- 매번 10,000줄 HTML을 대화 내역에 저장? → DB 폭발
- 매번 LLM에 전체 HTML 전송? → 토큰 낭비
해결: 파일 시스템 + 참조
핵심 아이디어:
- 실제 파일은 파일 시스템에 저장
- 대화 내역에는 "어떤 파일을 수정했는지" 메타데이터만 저장
구조:
파일 시스템:
/workspace/
└── sessions/
└── session-123/
└── index.html ← 실제 파일 (계속 업데이트)
데이터베이스:
conversations/
└── session-123/
└── messages: [
{
role: "user",
content: "버튼 색상 바꿔줘"
},
{
role: "assistant",
content: "수정했습니다",
tool_use: {
type: "edit_file",
file: "index.html" ← 파일 경로만
// 실제 내용은 저장 안 함
}
}
]
프로세스:
-
사용자 요청
- "버튼 색상 바꿔줘"
-
서버 처리
- 세션 작업 디렉토리에서
index.html읽기 - 현재 파일 내용 + 대화 내역을 LLM에 전송
- 세션 작업 디렉토리에서
-
LLM 응답
- Tool 사용:
edit_file - 변경 사항 반환
- Tool 사용:
-
서버 적용
- 파일 시스템의
index.html업데이트 - DB에는 "index.html을 수정했다"는 메타데이터만 저장
- 파일 시스템의
-
클라이언트 응답
- 수정된 HTML 전체를 반환
- 클라이언트는 최신 상태 표시
장점:
- DB 용량 절약 (파일 내용 저장 안 함)
- 토큰 효율적 (매번 현재 파일만 읽어서 전송)
- 최신 상태 보장
단점:
- 파일 시스템 관리 필요
- 버전 히스토리 별도 관리
버전 관리 옵션
옵션 A: Git 커밋 (Claude Code 방식)
각 편집마다 Git 커밋 생성
/workspace/sessions/session-123/
└── .git/
├── index.html
└── (git history)
장점:
- 모든 버전 보존
- Diff 확인 가능
- 롤백 쉬움
단점:
- Git 오버헤드
- 디스크 사용량 증가
옵션 B: Diff/Patch 저장
변경 사항(diff)만 저장
DB:
메시지 {
tool_use: {
type: "edit_file",
file: "index.html",
patch: "--- a/index.html
+++ b/index.html
@@ -10,7 +10,7 @@
-<button class='red'>
+<button class='blue'>"
}
}
장점:
- 저장 용량 최소화
- 변경 내역 추적
단점:
- 복원 시 순서대로 적용 필요
- 연산 오버헤드
옵션 C: 스냅샷 + 델타 (하이브리드)
N번마다 전체 스냅샷 + 중간은 델타
Turn 1: 전체 스냅샷 저장
Turn 2-9: 델타만 저장
Turn 10: 전체 스냅샷 저장
Turn 11-19: 델타만 저장
...
장점:
- 복원 빠름 (가장 가까운 스냅샷 + 델타)
- 저장 공간 절약
단점:
- 구현 복잡도 증가
실무 선택 기준
| 서비스 특성 | 추천 방법 |
|---|---|
| 간단한 채팅 | 파일 시스템만 |
| 버전 히스토리 중요 | Git 커밋 |
| 용량 최적화 중요 | Diff/Patch |
| 대규모 파일 | 스냅샷 + 델타 |
클라이언트-서버 통신
방법 A: 전체 파일 전송 (단순)
서버 → 클라이언트:
{
"message": "수정 완료",
"files": {
"index.html": "<html>...</html>" // 전체 내용
}
}
장점:
- 구현 간단
- 클라이언트가 최신 상태 보장
단점:
- 파일 크면 네트워크 부담
방법 B: Diff만 전송 (최적화)
서버 → 클라이언트:
{
"message": "수정 완료",
"diffs": {
"index.html": "--- a/index.html\n+++ b/index.html..."
}
}
클라이언트:
- Diff를 현재 파일에 적용
- 최신 상태 재구성
장점:
- 네트워크 효율적
단점:
- 클라이언트 복잡도 증가
- 동기화 오류 가능성
방법 C: WebSocket 실시간 동기화
파일 변경 시 즉시 푸시
서버 파일 변경 감지
→ WebSocket으로 클라이언트에 푸시
→ 클라이언트 UI 즉시 업데이트
장점:
- 실시간 동기화
- 여러 클라이언트 동시 편집 가능
단점:
- 인프라 복잡
- WebSocket 관리 필요
실무 아키텍처 정리
전체 구조
[클라이언트]
↓ (세션 ID + 메시지)
[API 서버]
↓ 조회
[DB: 대화 내역]
- 메타데이터만 (파일 경로, 수정 요약)
[API 서버]
↓ 읽기/쓰기
[파일 시스템]
- 실제 파일 (최신 상태)
[API 서버]
↓ 호출
[LLM API]
- 대화 내역 + 현재 파일 내용 전송
데이터 흐름
1. 사용자 요청
POST /api/chat
{
sessionId: "session-123",
message: "버튼 크기 키워줘"
}
2. 서버 처리
a. DB에서 대화 내역 조회 (파일 내용 제외)
b. 파일 시스템에서 index.html 읽기
c. 대화 내역 + 현재 파일을 LLM에 전송
d. LLM 응답 (edit tool 사용)
e. 파일 시스템에 변경사항 적용
f. DB에 메타데이터만 저장
3. 클라이언트 응답
{
message: "수정했습니다",
files: {
"index.html": "수정된 전체 HTML"
}
}
주요 설계 원칙
1. 대화 내역 ≠ 파일 내용
분리 저장:
- 대화 내역: DB (메타데이터, 대화 흐름)
- 파일 내용: 파일 시스템 (실제 데이터)
2. LLM에는 현재 상태만 전송
매번:
- 대화 내역 (최근 N개 or 토큰 제한 내)
- 현재 파일 내용 (파일 시스템에서 읽기)
절대:
- 모든 파일 버전 히스토리 전송 (X)
- 중복된 파일 내용 저장 (X)
3. 클라이언트는 최신 상태만 보유
클라이언트:
- 현재 보이는 파일 = 최신 버전
- 과거 버전은 서버에서 관리
4. 버전 관리는 선택적
필수:
- 현재 파일 저장
선택:
- Git 커밋 (버전 히스토리 중요할 때)
- Diff 저장 (용량 최적화 필요할 때)
성능 최적화
캐싱
자주 사용되는 시스템 프롬프트:
- LLM API 캐싱 활용 (Claude Prompt Caching)
- 토큰 비용 절감
세션 데이터:
- Redis에 캐싱
- DB 조회 최소화
병렬 처리
여러 작업 동시 처리:
- 대화 요약 + LLM 호출 병렬 실행
- 파일 읽기 + DB 조회 병렬 실행
스트리밍 응답
긴 응답은 스트리밍:
- Server-Sent Events (SSE)
- 사용자 경험 개선
- 타임아웃 방지
보안 고려사항
세션 격리
각 사용자의 작업 디렉토리 분리:
/workspace/
├── user-123/
│ └── session-456/
│ └── files...
└── user-789/
└── session-012/
└── files...
접근 제어
세션 소유권 확인:
- 다른 사용자의 세션 접근 차단
- JWT/세션 토큰 검증
파일 크기 제한
업로드/생성 제한:
- 최대 파일 크기 (예: 10MB)
- 총 스토리지 할당량
- 악용 방지
마치며
AI 채팅 서비스의 대화 관리는 생각보다 복잡하다. 하지만 핵심 원칙은 명확하다:
핵심 원칙:
- 대화 내역은 DB에, 파일은 파일 시스템에
- LLM에는 필요한 것만 전송
- 토큰 제한을 항상 고려
- 클라이언트는 단순하게, 서버가 복잡도 관리
단계별 구현:
- MVP: 슬라이딩 윈도우 + 파일 시스템
- 성장기: 토큰 기반 트리밍 + 캐싱
- 성숙기: 요약 + RAG + Git 버전 관리
처음부터 완벽할 필요는 없다. 사용자가 늘고 요구사항이 명확해지면서 점진적으로 개선하면 된다.
AI 채팅 서비스를 만들고 있다면, 이 글이 아키텍처 설계에 도움이 되길 바란다.