- Published on
AutoGPT 메모리 폭주 해결 - Redis TTL·요약
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
AutoGPT 계열 에이전트를 운영하다 보면 어느 순간부터 응답이 느려지고, Redis/벡터DB/파일 저장소가 기하급수적으로 커지면서 비용과 장애로 이어지는 경우가 많습니다. 원인은 단순히 “대화가 길어져서”가 아니라, 메모리 저장 정책이 무한히 누적되도록 설계되어 있거나, 요약 없이 원문을 계속 재주입하면서 토큰·저장·검색이 동시에 폭증하기 때문입니다.
이 글에서는 AutoGPT 메모리 폭주를 현실적으로 잡는 두 축을 다룹니다.
- Redis TTL로 “기억의 수명”을 강제해 저장소 폭주를 막기
- 요약(압축) 메모리로 “기억의 크기”를 제한해 토큰·검색 비용을 줄이기
추가로, 에이전트가 루프에 빠지며 메모리를 끝없이 쓰는 상황 자체를 막는 가드레일도 함께 연결합니다. 관련해서는 LangChain Agent 무한루프·토큰폭탄 차단 5팁 글이 매우 유용합니다.
AutoGPT 메모리 폭주의 전형적인 증상
운영에서 흔히 관측되는 시그널은 아래와 같습니다.
- Redis 메모리 사용량이 지속 상승하고,
maxmemory-policy로 인해 eviction이 발생 - 벡터DB upsert가 끝없이 늘어나 검색이 느려짐(인덱스 재빌드/compaction 비용 증가)
- 프롬프트가 비대해져 LLM 호출 토큰이 증가하고 지연이 커짐
- “같은 실수 반복”을 막으려다 오히려 모든 로그를 기억하려고 하면서 더 악화
핵심은 기억을 저장하는 비용과 기억을 다시 읽어오는 비용이 둘 다 폭주한다는 점입니다. 따라서 해결도 “저장 제한”과 “재주입 제한”을 함께 걸어야 효과가 큽니다.
원인 1: TTL 없는 키 누적(세션/에이전트 단위)
AutoGPT는 보통 아래 형태로 상태를 남깁니다.
- 세션별 대화 로그
- task별 intermediate thoughts, tool 결과
- 장기 메모리(벡터/문서)
이 중 세션성 데이터(최근 대화, tool 호출 결과 캐시 등)는 영구 보관할 이유가 없습니다. 하지만 TTL 없이 SET/HSET만 하면 Redis는 “영원히” 쌓입니다.
원인 2: 요약 없는 원문 재주입(컨텍스트 팽창)
대화가 길어질수록, 에이전트는 “정확도”를 위해 과거 로그를 더 많이 넣으려 합니다. 이때 요약 없이 원문을 넣으면
- LLM 입력 토큰 증가
- 응답 지연 증가
- 비용 증가
- 모델이 중요한 내용을 못 찾는 현상(신호 대비 잡음 증가)
이 문제는 저장소가 아니라 컨텍스트 설계 문제입니다.
해결 전략 개요: TTL로 수명 제한 + 요약으로 크기 제한
정리하면 다음과 같습니다.
- Redis에는 “세션성 메모리”를 저장하되 TTL을 강제한다
- 장기 메모리는 “원문”이 아니라 요약본 중심으로 축약하고, 원문은 필요할 때만 참조한다
- 요약은 단발성이 아니라, 누적 대화를 주기적으로 압축하는 방식이 운영에 유리하다
아래부터는 실제 구현 패턴을 코드로 설명합니다.
Redis TTL 설계: 무엇에 TTL을 걸 것인가
TTL을 걸기 좋은 데이터는 다음과 같습니다.
session:{id}:messages최근 대화session:{id}:tool_cache툴 결과 캐시session:{id}:scratchpad임시 계획/중간 결과
반대로 TTL을 신중히 해야 하는 데이터는 아래입니다.
- 결제/감사 로그 등 규정상 보관 필요 데이터
- 사용자 프로필/권한 등 핵심 상태
AutoGPT 메모리 폭주 해결에서 중요한 것은 세션 키에 TTL을 기본값으로 강제하는 것입니다.
Redis 키 네이밍과 TTL 예시
- 세션 대화:
autogpt:session:{sessionId}:messages - 세션 요약:
autogpt:session:{sessionId}:summary - 툴 캐시:
autogpt:session:{sessionId}:tool:{toolName}:cache
TTL 정책 예시:
- 대화/툴 캐시: 24시간
- 세션 요약: 7일(재방문 UX가 필요하면)
코드: Node.js에서 TTL 강제하기(ioredis)
아래는 리스트/스트림/해시 등 자료구조에 관계없이 “세션 키는 반드시 TTL을 가진다”를 구현하는 패턴입니다.
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);
const SESSION_TTL_SECONDS = 60 * 60 * 24; // 24h
function sessionKey(sessionId: string, suffix: string) {
return `autogpt:session:${sessionId}:${suffix}`;
}
async function pushMessage(sessionId: string, messageJson: string) {
const key = sessionKey(sessionId, "messages");
// MULTI로 원자적으로 push + expire
await redis
.multi()
.rpush(key, messageJson)
.expire(key, SESSION_TTL_SECONDS)
.exec();
}
async function setToolCache(sessionId: string, toolName: string, cacheKey: string, valueJson: string) {
const key = sessionKey(sessionId, `tool:${toolName}:cache`);
// 해시 필드 업데이트 후 TTL 갱신
await redis
.multi()
.hset(key, cacheKey, valueJson)
.expire(key, SESSION_TTL_SECONDS)
.exec();
}
포인트는 expire를 “한 번만” 거는 게 아니라, 쓰기 작업마다 TTL을 갱신해 세션이 활성인 동안만 유지되게 하는 것입니다.
자주 하는 실수: SETEX만 쓰고 끝내기
SETEX는 문자열 키에는 편하지만, 리스트/해시/셋을 쓰면 TTL을 놓치기 쉽습니다. 그래서 위처럼 MULTI로 쓰기와 TTL을 묶는 습관이 안전합니다.
Redis 메모리 상한과 eviction 정책도 함께 설정
TTL만으로도 대부분은 해결되지만, 운영에서는 “버그나 루프”로 TTL 갱신이 계속되며 무한히 커질 수 있습니다. Redis에는 마지막 방어선을 둡니다.
maxmemory설정maxmemory-policy는 보통allkeys-lru또는volatile-ttl을 고려
다만 eviction은 데이터 손실을 의미하므로, TTL로 1차 제어하고 eviction은 “장애 방지용”으로 두는 게 좋습니다.
요약(압축) 메모리: 컨텍스트를 작게 유지하는 핵심
TTL이 저장 폭주를 막는다면, 요약은 다음을 해결합니다.
- LLM 입력 토큰 절감
- 검색 대상 축소
- 중요한 사실을 구조화해 재사용
요약은 단순히 “한 줄로 줄이기”가 아니라, 에이전트가 다시 활용할 수 있는 형태로 만드는 게 중요합니다.
추천 요약 포맷(구조화)
- 목표/요구사항
- 확정된 결정 사항
- 미해결 이슈
- 사용자 선호(톤, 언어, 금지사항)
- 다음 액션
이렇게 하면 대화 원문을 다 넣지 않아도, 요약만으로 다음 턴을 안정적으로 이어갈 수 있습니다.
코드: 대화 N턴마다 요약 갱신(슬라이딩 윈도우)
아래 예시는
- 최근 메시지 20개는 원문으로 유지
- 그 이전은 요약으로 합쳐서
summary에 저장
하는 방식입니다.
import OpenAI from "openai";
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const SESSION_TTL_SECONDS = 60 * 60 * 24;
const SUMMARY_TTL_SECONDS = 60 * 60 * 24 * 7;
function key(sessionId: string, suffix: string) {
return `autogpt:session:${sessionId}:${suffix}`;
}
async function maybeSummarize(sessionId: string) {
const messagesKey = key(sessionId, "messages");
const summaryKey = key(sessionId, "summary");
const len = await redis.llen(messagesKey);
const WINDOW = 20;
const THRESHOLD = 60;
if (len <= THRESHOLD) return;
// 요약 대상: 앞부분(len - WINDOW)
const toSummarize = await redis.lrange(messagesKey, 0, len - WINDOW - 1);
const recent = await redis.lrange(messagesKey, len - WINDOW, -1);
const prevSummary = (await redis.get(summaryKey)) ?? "";
const prompt = [
"You are summarizing an agent conversation for future context.",
"Update the running summary with the new chunk.",
"Return JSON with keys: goals, decisions, open_issues, user_prefs, next_actions.",
"Keep it concise and factual.",
"\nPREVIOUS_SUMMARY:\n" + prevSummary,
"\nNEW_MESSAGES:\n" + toSummarize.join("\n"),
].join("\n");
const resp = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
temperature: 0.2,
});
const newSummary = resp.choices[0]?.message?.content ?? "";
// 원자적 업데이트: 요약 저장 + 메시지 리스트를 recent만 남기기
await redis
.multi()
.set(summaryKey, newSummary, "EX", SUMMARY_TTL_SECONDS)
.del(messagesKey)
.rpush(messagesKey, ...recent)
.expire(messagesKey, SESSION_TTL_SECONDS)
.exec();
}
이 방식의 장점:
- 대화가 길어져도 입력 컨텍스트는
summary + recent로 일정하게 유지 - Redis 공간도 선형이 아니라 상수에 가까운 형태로 유지
MDX/운영 관점 팁: 요약 JSON은 파싱 가능해야 한다
요약을 자연어로만 저장하면, 나중에 “사용자 선호”나 “미해결 이슈”를 안정적으로 뽑기 어렵습니다. 최소한 키를 고정한 JSON으로 받으면, 에이전트 정책(금지사항, 도구 사용 제한 등)에 재활용하기 좋습니다.
장기 메모리(벡터)도 요약 중심으로 재설계
AutoGPT가 벡터DB에 원문을 계속 넣으면, 검색 결과가 과다해지고 관련도도 떨어집니다. 권장 패턴은 다음입니다.
- 원문은 객체 스토리지나 DB에 저장하되, 벡터에는 요약/키워드/사실만 넣기
- 벡터 메타데이터에
sessionId,topic,createdAt,ttlHint를 넣고 주기적으로 정리
즉, 벡터DB는 “기억 창고”라기보다 “기억 색인”에 가깝게 운영해야 합니다.
폭주를 더 키우는 요인: 에이전트 루프와 툴 스팸
메모리 폭주는 종종 “저장 정책” 문제가 아니라 에이전트가 같은 툴을 반복 호출하면서 로그가 폭증해 생깁니다. 이 경우 TTL과 요약만으로는 비용을 근본적으로 못 잡을 수 있습니다.
- 동일한 입력으로 같은 툴을 반복 호출
- 실패한 API를 재시도하며 로그가 계속 쌓임
- 계획이 갱신되지 않아 무한 루프
이때는 루프 차단 가드레일을 같이 넣어야 합니다. 아래 글을 함께 참고하면 좋습니다.
운영 체크리스트: “폭주”를 재발하지 않게 만들기
1) 관측(Observability)
- Redis 키스페이스 크기:
INFO keyspace,MEMORY STATS - 세션당 메시지 길이/요약 길이
- LLM 호출당 input 토큰 추이
- 툴 호출 횟수 상한(세션당, 분당)
2) 정책(Policy)
- 세션 키는 기본 TTL 강제(쓰기 때마다
expire갱신) - 요약은 N턴마다 갱신, 최근 윈도우만 원문 유지
- 벡터DB는 원문 대신 요약/사실 중심
3) 장애 대비
- Redis
maxmemory와 eviction 정책 설정 - 백그라운드 정리 작업(오래된 세션/인덱스 정리)
- 루프 감지 시 세션 중단 또는 강제 요약 후 종료
결론: TTL은 “수명”, 요약은 “크기”를 다룬다
AutoGPT 메모리 폭주는 한 가지 처방으로는 잘 안 잡힙니다.
- Redis TTL로 세션성 데이터를 “언젠가 반드시 사라지게” 만들고
- 요약 메모리로 컨텍스트를 “항상 작게” 유지하며
- 루프/툴 스팸을 막는 가드레일까지 더하면
장시간 실행에도 비용과 지연이 안정적으로 유지됩니다.
이미 운영 중이라면, 우선순위는 세션 키 TTL 강제를 1순위로 적용하고, 그 다음 요약 + 최근 윈도우로 프롬프트 크기를 고정하는 것을 추천합니다. 이 두 가지가 들어가면 “메모리 폭주”의 80%는 즉시 체감될 정도로 개선됩니다.