- Published on
AutoGPT 메모리 누수, SQLite 체크포인트로 잡기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
AutoGPT나 유사 에이전트를 장시간 실행하다 보면, 어느 순간부터 프로세스 RSS가 꾸준히 증가하고 결국 OOM으로 죽는 패턴을 겪곤 합니다. 많은 경우 이 현상은 “순수한 파이썬 메모리 누수”가 아니라, SQLite의 WAL(Write-Ahead Logging) 파일이 계속 커지고 체크포인트가 제때 일어나지 않으면서 디스크와 페이지 캐시가 팽창하는 문제로 관찰됩니다. 특히 AutoGPT처럼 에이전트 메모리(대화 기록, 작업 로그, 벡터/요약 결과)를 자주 쓰는 워크로드에서는 이 문제가 더 빨리 드러납니다.
이 글에서는 AutoGPT 실행 중 메모리 증가를 “SQLite 체크포인트 전략”으로 완화하는 방법을 설명합니다. 단순히 PRAGMA wal_checkpoint 한 번 호출하는 수준이 아니라, 왜 WAL이 커지는지, 어떤 체크포인트 모드가 어떤 트레이드오프를 갖는지, 운영에서 안전하게 자동화하는 방법까지 다룹니다.
또한 에이전트가 무한히 로그를 쌓아 토큰과 비용이 폭주하는 문제는 별도 원인이지만, 함께 나타나는 경우가 많습니다. 관련해서는 LangChain Agent 무한루프·토큰폭탄 차단 5팁도 같이 참고하면 좋습니다.
1) “메모리 누수”처럼 보이는 대표 증상
다음 중 하나라도 맞으면 SQLite WAL/체크포인트를 먼저 의심해볼 만합니다.
- AutoGPT를 오래 돌리면 RSS가 단조 증가하고, 재시작하면 정상으로 돌아온다
- 컨테이너 환경에서
memory.limit에 닿기 전 디스크 IO가 이상하게 증가한다 - 작업 디렉터리에
*.db-wal파일이 계속 커지는데 줄어들지 않는다 - 프로세스가 죽기 직전, DB 쓰기 지연이 커지고 응답이 느려진다
중요한 점은, RSS 증가가 곧 “파이썬 객체가 해제되지 않는다”는 뜻은 아니라는 겁니다. SQLite WAL은 디스크 파일이지만, OS 페이지 캐시로 인해 메모리 사용량이 함께 증가하는 것처럼 보일 수 있습니다.
2) SQLite WAL과 체크포인트: 핵심 개념만 빠르게
SQLite에서 WAL 모드는 쓰기를 DB 본체 파일이 아니라 WAL 파일에 append 합니다. 읽기는 DB 본체와 WAL을 함께 참조합니다.
- 장점: 동시성(읽기-쓰기)에서 유리, 쓰기 성능이 안정적
- 단점: 체크포인트가 안 되면 WAL 파일이 계속 커짐
체크포인트는 WAL에 쌓인 변경 내용을 DB 본체로 “합치는” 작업입니다. 체크포인트가 자주, 잘 일어나면 WAL은 적당히 유지되고 페이지 캐시/디스크 사용량도 안정됩니다.
3) 왜 AutoGPT에서 WAL이 과하게 커지나
AutoGPT류 워크로드는 다음 특성을 가집니다.
- 짧은 주기로 작은 쓰기가 매우 많이 발생(로그, 메모리, 태스크 상태)
- 에이전트 루프가 길어질수록 쓰기 횟수가 증가
- 백그라운드에서 읽기/쓰기 경쟁이 생기기 쉬움
- 컨테이너/서버리스에서 디스크가 느리거나 제한적일 수 있음
여기서 체크포인트가 늦어지는 전형적인 원인은 다음입니다.
- 장시간 열린 트랜잭션(읽기 트랜잭션 포함)이 체크포인트를 막음
busy_timeout이 짧아 체크포인트가 자주 실패- 기본 자동 체크포인트 임계치가 워크로드에 비해 너무 큼
- 애플리케이션이
WAL을 켰지만 운영에서 점검/압축 루틴이 없음
4) 진단: WAL 파일과 체크포인트 상태 확인
4-1) 파일 크기부터 확인
리눅스에서 DB 파일과 WAL 크기를 먼저 봅니다.
ls -lh /path/to/autogpt.db /path/to/autogpt.db-wal /path/to/autogpt.db-shm
*.db-wal이 수백 MB에서 수 GB까지 커져 있고 줄어들지 않으면, 체크포인트 정책이 사실상 동작하지 않는 상황일 가능성이 큽니다.
4-2) SQLite에서 WAL 모드 및 체크포인트 결과 확인
sqlite3 /path/to/autogpt.db <<'SQL'
PRAGMA journal_mode;
PRAGMA wal_autocheckpoint;
PRAGMA busy_timeout;
PRAGMA wal_checkpoint(PASSIVE);
SQL
출력에서 체크포인트 결과는 보통 3개 숫자로 나오는데(버전에 따라 표현이 다를 수 있음), 대략적으로
- 체크포인트가 얼마나 진행됐는지
- 남아 있는 프레임 수
를 판단하는 데 사용합니다. PASSIVE에서 남은 프레임이 계속 크면, 더 강한 모드가 필요하거나(또는) 체크포인트를 막는 읽기 트랜잭션이 있는지 확인해야 합니다.
5) 해결 전략: “자동 체크포인트 + 주기적 강제 체크포인트 + 정리”
운영에서 가장 현실적인 조합은 아래 3단계입니다.
- WAL 모드 설정을 명시하고, 자동 체크포인트 임계치를 낮춘다
- 주기적으로
wal_checkpoint(TRUNCATE)같은 강한 체크포인트를 실행한다 - 필요 시
VACUUM또는incremental_vacuum으로 DB 본체 파일까지 정리한다
핵심은 “WAL만 줄여도” 체감 메모리/디스크 압박이 크게 줄 수 있지만, DB 본체 파일이 계속 커지는 구조(삭제가 많음)라면 VACUUM도 같이 고려해야 한다는 점입니다.
6) 코드 예제: 파이썬에서 SQLite 체크포인트 자동화
아래 코드는 AutoGPT 실행 프로세스 내부에 넣어도 되고, 별도 사이드카/크론 잡으로 돌려도 됩니다. 프로세스 내부에 넣을 때는 DB 커넥션을 분리해서 짧게 열고 닫는 방식이 안전합니다.
6-1) 안전한 체크포인트 함수
import sqlite3
import time
from contextlib import closing
def checkpoint_sqlite(db_path: str) -> dict:
"""WAL을 줄이고 파일을 안정화하기 위한 체크포인트 루틴."""
with closing(sqlite3.connect(db_path, timeout=30)) as conn:
conn.isolation_level = None # autocommit
cur = conn.cursor()
# 동시성 환경에서 최소한의 대기
cur.execute("PRAGMA busy_timeout = 30000;")
# WAL 모드 강제(이미 WAL이라도 명시)
cur.execute("PRAGMA journal_mode = WAL;")
# 자동 체크포인트 임계치(페이지 단위). 워크로드에 맞게 조정.
# 예: 1000 페이지면 대략 수 MB ~ 수십 MB 수준에서 자주 합쳐짐
cur.execute("PRAGMA wal_autocheckpoint = 1000;")
# 강제 체크포인트: TRUNCATE는 WAL 파일을 잘라내는 데 유리
cur.execute("PRAGMA wal_checkpoint(TRUNCATE);")
row = cur.fetchone()
return {"wal_checkpoint_result": row}
if __name__ == "__main__":
db = "/path/to/autogpt.db"
while True:
result = checkpoint_sqlite(db)
print(result)
time.sleep(60)
모드 선택 가이드
PASSIVE: 다른 커넥션을 거의 방해하지 않지만 WAL이 잘 안 줄 수 있음FULL: 더 적극적으로 체크포인트를 수행RESTART: WAL을 새로 시작(파일 크기 자체가 줄지는 않을 수 있음)TRUNCATE: WAL 파일을 잘라 크기 감소에 가장 직관적(다만 IO 부담 증가 가능)
대부분 “메모리 누수처럼 보이는 WAL 팽창”을 잡는 목적이라면, 주기적으로 TRUNCATE를 시도하고 실패하면 다음 주기에 재시도하는 접근이 실용적입니다.
7) 운영 팁: 체크포인트가 실패하는 흔한 이유와 대응
7-1) 장시간 읽기 트랜잭션이 체크포인트를 막는다
SQLite WAL은 읽기 트랜잭션이 오래 유지되면, 해당 시점 이전 프레임을 정리하지 못해 WAL이 커질 수 있습니다. 해결책은 다음입니다.
- 읽기 커넥션을 오래 붙잡지 않기
- 커넥션 풀 사용 시, 커넥션 반환이 확실한지 점검
- 대량 조회를 작은 페이지로 나누기
7-2) busy_timeout이 너무 짧다
경합이 있는 환경에서 체크포인트는 lock을 필요로 합니다. busy_timeout이 짧으면 체크포인트가 자주 실패하고 WAL은 계속 누적됩니다. 위 코드처럼 30000ms 정도로 늘려보고, 관측하면서 조정합니다.
7-3) 컨테이너 환경에서 디스크 IO가 병목
체크포인트는 결국 디스크에 쓰는 작업입니다. IO가 느리면 체크포인트가 뒤처지고 WAL이 커집니다.
- 저장소 클래스(예: 네트워크 디스크)를 점검
- 체크포인트 주기를 늘리되,
wal_autocheckpoint를 낮춰 “자주 조금씩” 합치기 - 로그 폭주로 디스크가 꽉 차는 상황도 함께 점검
로그가 디스크를 압박해 연쇄적으로 DB IO가 느려지는 경우도 많습니다. 이때는 리눅스 journald 로그 폭주로 디스크 꽉 찰 때 해결처럼 로그 상한을 걸어주는 것이 DB 안정화에 간접적으로 도움이 됩니다.
8) VACUUM까지 필요할 때: DB 본체 파일이 계속 커지는 경우
WAL을 줄였는데도 *.db 파일 자체가 계속 커진다면, “삭제된 공간이 DB 파일에 남아 있는” 상태일 수 있습니다. 이때는 VACUUM을 고려합니다.
주의: VACUUM은 DB 전체를 재작성하므로 비용이 큽니다. 트래픽이 낮은 시간대에 수행하거나, auto_vacuum을 incremental로 바꾸는 방법을 고려하세요.
8-1) 점검 및 VACUUM 예시
sqlite3 /path/to/autogpt.db <<'SQL'
PRAGMA freelist_count;
PRAGMA page_count;
VACUUM;
SQL
freelist_count가 크면 내부적으로 비어 있는 페이지가 많다는 뜻입니다.
9) “진짜” 메모리 누수와 구분하는 체크리스트
SQLite 체크포인트로 완화가 되면, 원인은 대체로 WAL/IO/캐시 쪽이었을 확률이 큽니다. 그래도 RSS가 계속 증가한다면 아래도 같이 보세요.
- 에이전트가 대화/툴 결과를 무한히 누적하고 있는지
- 스트리밍 호출이 실패 재시도로 폭주하는지
- 벡터 스토어 클라이언트가 커넥션을 누수하는지
특히 LLM 호출이 429 재시도로 폭주하면 로그와 DB 기록도 같이 폭증합니다. 이 경우에는 LangChain OpenAI 스트리밍 중 429 폭주 해결법을 함께 적용해 “입력 폭주 자체”를 먼저 막는 게 효과적입니다.
10) 권장 설정 조합(현실적인 기본값)
AutoGPT 같은 단일 노드 SQLite 운영을 전제로, 다음 조합이 무난한 출발점입니다.
journal_mode = WALbusy_timeout = 30000wal_autocheckpoint = 500에서2000사이로 테스트- 30초에서 120초 간격으로
wal_checkpoint(TRUNCATE)시도 - 주 1회 또는 트래픽 저점에
VACUUM(필요한 경우에만)
중요한 건 “정답 값”이 아니라, 워크로드(쓰기 빈도, DB 크기, 디스크 성능)에 따라 관측 기반으로 조정해야 한다는 점입니다.
마무리
AutoGPT 장시간 실행에서 보이는 메모리 증가가 항상 파이썬 레벨 누수는 아닙니다. SQLite WAL이 체크포인트 없이 누적되면 디스크뿐 아니라 페이지 캐시로 인해 메모리도 같이 잠식하는 것처럼 보일 수 있습니다.
해결의 핵심은 wal_autocheckpoint로 기본 동작을 촘촘히 만들고, 운영에서 주기적으로 wal_checkpoint(TRUNCATE)를 실행해 WAL 파일이 “계속 커지기만 하는” 상태를 끊는 것입니다. 여기에 필요 시 VACUUM까지 더하면, AutoGPT의 장시간 안정성이 체감될 정도로 개선되는 경우가 많습니다.