- Published on
AutoGPT 메모리 폭주 해결 - Qdrant 튜닝 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
AutoGPT를 돌리다 보면 어느 순간부터 RAM이 치솟고, 디스크도 같이 불어나며, 결국 컨테이너가 OOMKilled로 죽는 패턴을 자주 봅니다. 표면적으로는 “LLM이 답을 길게 해서”처럼 보이지만, 실제로는 메모리(장기기억) 저장 전략 + 벡터DB(Qdrant) 설정 + 인덱싱/세그먼트 정책이 합쳐져 폭주하는 경우가 많습니다.
이 글은 AutoGPT류 에이전트가 Qdrant에 임베딩을 계속 쌓을 때 발생하는 메모리 폭주를 재현 가능한 체크리스트로 진단하고, Qdrant를 중심으로 튜닝하는 방법을 정리합니다.
폭주의 전형적 원인: “기억을 무한히 쌓는 구조”
AutoGPT 계열의 메모리 파이프라인은 대체로 다음 흐름입니다.
- 프롬프트/툴 결과/중간 사고(Chain-of-thought 유사 로그 포함)를 텍스트로 저장
- 임베딩 생성
- Qdrant에
upsert - 다음 스텝에서
search로 유사 컨텍스트를 끌어옴
문제는 1~3이 제한 없이 반복될 때입니다. 특히 아래 조건이 겹치면 폭주가 빨라집니다.
- 중간 로그(실패/재시도/툴 출력)가 길고 자주 저장됨
- 동일한 사실/문서가 중복 저장됨(중복 제거 부재)
- payload에 원문 전체를 저장하고, 반환 결과에서도 payload를 전부 가져옴
- 컬렉션이
on_disk_payload=false로 커진 payload를 RAM에 오래 잡음 - HNSW 인덱스가 너무 이른 시점에 빌드되어 메모리 사용량이 급증
먼저 확인할 지표: Qdrant가 “메모리”를 어디에 쓰는가
Qdrant에서 메모리 사용량은 크게 아래로 나뉩니다.
- 벡터 저장소: 벡터 자체(예: 1536 float32)와 세그먼트 메타
- HNSW 인덱스: 그래프 구조가 메모리를 많이 씀
- payload 인덱스: 필터링을 위한 인덱스(필요할 때만)
- payload 캐시: payload를 디스크에서 읽어올 때 캐시
따라서 “RAM이 늘었다”는 증상만으로는 원인을 특정하기 어렵고, 아래 순서로 접근하는 게 좋습니다.
- 포인트 수 증가 속도: 초당/분당 몇 건 upsert 되는지
- 벡터 차원과 dtype: 1536 float32면 포인트 100만에서 벡터만 대략
1536*4바이트 단위로 커짐 - payload 크기: 원문을 통째로 넣으면 벡터보다 payload가 더 커질 수도 있음
- 인덱싱 타이밍: 인덱스가 빌드되는 순간 메모리 스파이크가 발생
운영 중 디스크가 줄지 않는 이슈가 함께 보이면, 로그/스냅샷/삭제 파일 핸들 점유도 같이 의심해야 합니다. 이런 케이스는 logrotate 후 디스크가 안 줄 때 - 삭제 파일 점유 해결도 함께 참고하면 좋습니다.
핵심 처방 1: 컬렉션 스키마를 “메모리 친화적”으로 다시 만든다
AutoGPT 메모리 컬렉션을 만들 때, 기본값으로 두면 payload가 RAM에 남거나 인덱스가 과하게 잡히는 경우가 있습니다. 아래는 튜닝 포인트입니다.
1) payload는 디스크로: on_disk_payload=true
payload에 원문 텍스트를 넣는다면, RAM에 올려두는 순간 폭주의 주범이 됩니다. 가능하면 payload는 디스크에 두고, 필요할 때만 읽게 하세요.
curl -X PUT "http://localhost:6333/collections/autogpt_memory" \
-H 'Content-Type: application/json' \
-d '{
"vectors": {"size": 1536, "distance": "Cosine"},
"on_disk_payload": true
}'
이미 컬렉션이 있다면, 운영에서는 마이그레이션이 필요할 수 있습니다. 신규 컬렉션을 만들고 배치로 옮기는 편이 안전합니다.
2) 벡터를 디스크로: on_disk=true (대규모일 때)
포인트 수가 수십만~수백만을 넘어가면 벡터 자체도 부담입니다. Qdrant는 벡터를 디스크에 두는 옵션을 제공합니다.
curl -X PATCH "http://localhost:6333/collections/autogpt_memory" \
-H 'Content-Type: application/json' \
-d '{
"optimizers_config": {
"default_segment_number": 2
},
"vectors": {
"on_disk": true
}
}'
디스크 기반은 latency가 증가할 수 있으니, “장기기억” 영역에만 적용하고 “작업 메모리”는 별도 컬렉션으로 RAM 기반을 유지하는 식의 분리가 실무에서 효과적입니다.
3) HNSW 파라미터를 줄여 메모리 상한을 잡는다
HNSW는 m과 ef_construct가 메모리와 빌드 비용에 큰 영향을 줍니다.
m: 그래프 연결 수. 크면 recall은 좋아지지만 메모리 증가ef_construct: 인덱스 빌드 품질. 크면 빌드 시간/메모리 증가
AutoGPT 메모리는 “완벽한 검색”보다 “대충 관련 있는 기억”이 중요할 때가 많아서, 과한 HNSW는 낭비가 됩니다.
curl -X PATCH "http://localhost:6333/collections/autogpt_memory" \
-H 'Content-Type: application/json' \
-d '{
"hnsw_config": {
"m": 8,
"ef_construct": 64,
"full_scan_threshold": 20000
}
}'
full_scan_threshold를 적절히 두면, 데이터가 작을 때는 인덱스 없이 스캔하고 커지면 인덱스를 쓰는 식으로 전환되어 초기 메모리 스파이크를 줄이는 데 도움이 됩니다.
핵심 처방 2: “무한 적재”를 막는 데이터 전략(중복 제거 + TTL)
Qdrant 튜닝만으로도 개선되지만, AutoGPT 메모리 폭주의 본질은 데이터가 계속 쌓이는 것입니다. 다음 두 가지가 가장 효과가 큽니다.
1) 중복 제거: 해시 기반 id를 강제한다
동일 문장을 매번 새 포인트로 넣지 말고, 내용 기반 해시를 id로 사용하면 upsert가 덮어쓰게 만들 수 있습니다.
import hashlib
from qdrant_client import QdrantClient
from qdrant_client.http.models import PointStruct
client = QdrantClient(url="http://localhost:6333")
def content_id(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def upsert_memory(collection: str, text: str, vector: list[float], meta: dict):
pid = content_id(text)
client.upsert(
collection_name=collection,
points=[
PointStruct(
id=pid,
vector=vector,
payload={
"text": text,
**meta,
},
)
],
)
이때 인코딩 문제가 섞이면 해시가 달라져 중복 제거가 깨질 수 있습니다. 크롤링/파일 입력을 섞어 쓰는 파이프라인이라면 Python UnicodeDecodeError - utf-8 실전 해결법처럼 입력을 utf-8로 정규화하는 습관이 중요합니다.
2) TTL(수명) 설계: “기억”에도 유통기한을 둔다
AutoGPT 메모리는 보통 다음 두 레이어로 나누면 안정적입니다.
- 작업 메모리: 최근 N시간~N일 (짧은 TTL)
- 장기 메모리: 요약/정제된 사실만 (긴 TTL 또는 수동 유지)
Qdrant 자체 TTL 기능은 버전/구성에 따라 접근 방식이 달라질 수 있어, 실무에서는 payload에 created_at을 넣고 주기적으로 삭제하는 배치가 가장 단순합니다.
import time
from qdrant_client import QdrantClient
from qdrant_client.http import models
client = QdrantClient(url="http://localhost:6333")
def delete_older_than(collection: str, max_age_seconds: int):
threshold = int(time.time()) - max_age_seconds
client.delete(
collection_name=collection,
points_selector=models.FilterSelector(
filter=models.Filter(
must=[
models.FieldCondition(
key="created_at",
range=models.Range(lte=threshold),
)
]
)
),
)
이 배치를 크론으로 하루 1회만 돌려도 폭주를 크게 줄일 수 있습니다.
핵심 처방 3: payload를 “검색용”과 “표시용”으로 분리한다
AutoGPT는 검색 결과에서 payload를 그대로 프롬프트에 붙이는 경우가 많습니다. 이때 payload가 크면
- Qdrant에서 읽어오는 비용 증가
- 네트워크 전송량 증가
- LLM 컨텍스트 낭비
가 동시에 발생합니다.
권장 패턴은 다음입니다.
- Qdrant payload에는 요약문/키워드/문서 ID만 저장
- 원문은 별도 스토리지(PostgreSQL, S3, 파일)로 저장
- 필요할 때만 원문을 가져오고, 프롬프트에는 요약을 우선 사용
예시 payload 설계:
{
"doc_id": "kb-2026-02-25-001",
"summary": "장기기억: Qdrant on_disk_payload로 RAM 폭주를 줄인다.",
"tags": ["qdrant", "autogpt", "memory"],
"created_at": 1700000000
}
그리고 search 시에는 with_payload를 최소화합니다(클라이언트 옵션으로 필요한 필드만 가져오기).
핵심 처방 4: 필터 인덱스는 필요한 것만 만든다
payload 필드에 대해 무분별하게 인덱스를 만들면 메모리/디스크가 늘고, 세그먼트 최적화 비용도 증가합니다.
AutoGPT 메모리에서 인덱싱 가치가 큰 필드는 보통 아래 정도입니다.
created_at(TTL 삭제/기간 필터)tags또는namespace(프로젝트/에이전트 분리)doc_type(요약 vs 원문 vs 로그 구분)
필드 인덱스 생성 예시:
curl -X PUT "http://localhost:6333/collections/autogpt_memory/index" \
-H 'Content-Type: application/json' \
-d '{
"field_name": "created_at",
"field_schema": "integer"
}'
핵심 처방 5: 세그먼트/옵티마이저 튜닝으로 “스파이크”를 줄인다
Qdrant는 백그라운드에서 세그먼트 머지/최적화를 수행합니다. 이 과정에서 CPU/IO/RAM 스파이크가 발생할 수 있고, AutoGPT처럼 지속적으로 쓰기(upsert)가 들어오면 스파이크가 더 잦아집니다.
접근 방법:
- 너무 잦은 최적화를 피하도록 옵티마이저 임계값 조정
- 스냅샷/백업 주기를 조절해 IO 경합을 줄임
- 컨테이너 리소스 제한을 현실적으로 설정하고, OOM이 나면 Qdrant가 아닌 “에이전트”를 먼저 죽이도록 분리 배치
운영 환경에서 이런 리소스 경합이 DB 락/대기처럼 보일 때도 있습니다. 데이터 저장소를 PostgreSQL로 같이 쓰는 구조라면 PostgreSQL 락 대기 폭증? deadlock 진단·해결처럼 병목을 함께 점검하세요.
실전 구성 예시: 작업 메모리/장기 메모리 2컬렉션
가장 안정적인 패턴은 Qdrant 컬렉션을 2개로 쪼개는 것입니다.
autogpt_working_memory- 최근 대화/툴 결과 요약
- TTL 짧음(예: 24~72시간)
- 성능 우선(필요하면 RAM)
autogpt_long_memory- 검증된 사실/요약만 저장
- TTL 길거나 수동 큐레이션
on_disk_payload=true, 필요 시 벡터도 디스크
검색은 보통 다음 순서가 좋습니다.
- 작업 메모리에서
top_k작게(예: 5~10) - 부족하면 장기 메모리에서 추가(예: 5)
- 합친 뒤 중복 제거 후 프롬프트에 삽입
이렇게 하면 장기 메모리의 규모가 커져도 작업 성능이 급격히 떨어지지 않습니다.
체크리스트: 튜닝 전후로 꼭 비교할 것
- 포인트 증가량: 분당 upsert 수, 중복 제거 후 감소했는지
- 평균 payload 크기: 원문을 빼고 요약으로 줄었는지
- Qdrant RAM:
on_disk_payload적용 후 안정화되는지 - 인덱스 빌드 스파이크: HNSW 파라미터 조정 후 피크가 줄었는지
- 검색 품질: recall이 약간 떨어져도 에이전트 성능이 유지되는지(대부분 유지됨)
마무리
AutoGPT 메모리 폭주는 “Qdrant가 느리다/무겁다”의 문제가 아니라, 기억을 무제한 적재하는 제품 구조가 먼저이고, 그 다음이 Qdrant의 payload/인덱스/세그먼트 정책입니다.
정리하면 우선순위는 다음이 가장 효과적입니다.
- 중복 제거(내용 해시
id)로 쓰기량 자체를 줄이기 - TTL로 상한을 만들기
on_disk_payload=true로 RAM 폭주 차단- HNSW 파라미터를 보수적으로 조정
- 작업/장기 메모리 컬렉션 분리
이 5가지만 적용해도 AutoGPT를 장시간 돌릴 때 “처음엔 되다가 며칠 뒤 죽는” 패턴을 대부분 끊어낼 수 있습니다.