- Published on
AutoGPT 메모리 누수? SQLite 체크포인트 최적화
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 AutoGPT를 장시간 돌리다 보면, 프로세스 RSS가 완만하게(혹은 계단식으로) 계속 증가해 “메모리 누수”처럼 보이는 경우가 있습니다. 실제로는 파이썬 힙 누수라기보다 SQLite WAL 모드에서 체크포인트가 제때 수행되지 않아 WAL 파일이 비대해지고, 그 과정에서 페이지 캐시/IO 대기가 늘어 메모리 압박과 지연이 함께 나타나는 패턴이 흔합니다.
이 글은 AutoGPT류 에이전트가 로컬 상태(태스크 로그, 이벤트, 임시 결과, 벡터 인덱스 메타 등)를 SQLite로 저장할 때 발생하는 WAL 체크포인트 병목을 진단하고, 운영 환경에서 안전하게 최적화하는 방법을 다룹니다.
관련해서 벡터DB 쪽 데이터 팽창이 메모리/디스크를 밀어 올리는 케이스도 많습니다. SQLite 튜닝과 함께 아래 글도 같이 보면 원인 분리가 빨라집니다.
증상: “메모리 누수처럼 보이는” SQLite WAL 병목
다음 중 2개 이상이면 WAL 체크포인트를 의심할 가치가 큽니다.
- 프로세스 RSS가 서서히 증가하고, 재시작하면 즉시 내려간다.
- 디스크에
*.db-wal파일이 계속 커진다(수백 MB~수 GB). - 쓰기 트래픽이 있는 동안 읽기 지연이 늘거나, 간헐적 타임아웃이 뜬다.
- 컨테이너 환경에서 디스크 IO가 튀고, CPU는 낮은데 전체 처리량이 떨어진다.
핵심은 SQLite의 WAL 모드가 “쓰기 성능”을 주는 대신 WAL 파일을 주기적으로 메인 DB로 병합(checkpoint) 해야 안정적이라는 점입니다. 체크포인트가 안 되면 WAL이 누적되고, 읽기 시점에 더 많은 페이지를 따라가야 하며, 파일 시스템 캐시와 SQLite 페이지 캐시가 함께 커지면서 결과적으로 메모리 사용이 늘어 보일 수 있습니다.
SQLite WAL과 체크포인트 동작 요약
- WAL 모드에서는 쓰기가
db-wal에 append 됩니다. - 체크포인트는 WAL의 변경 내용을 메인
db파일로 옮기고, WAL을 재사용 가능한 상태로 만듭니다. - 체크포인트가 너무 자주면 쓰기 지연이 늘고, 너무 드물면 WAL이 비대해지고 복구 비용이 증가합니다.
SQLite는 기본적으로 자동 체크포인트(프레임 수 기준)를 수행하지만, 다음 같은 상황에서 “자동 체크포인트가 충분히 작동하지 않는 것처럼” 보일 수 있습니다.
- 장시간 유지되는 읽기 트랜잭션이 있어 체크포인트가 진행되지 못함
- 다중 프로세스/다중 스레드에서 락 경합으로 체크포인트가 밀림
- busy timeout이 짧아 체크포인트 시도가 실패하고 재시도가 늦음
- 페이지 크기/캐시/동기화 설정이 워크로드에 맞지 않음
1단계: 현재 상태를 계측해서 원인 확정하기
WAL 파일 크기 확인
운영 중에 WAL 파일이 계속 커지는지부터 확인합니다.
ls -lh ./data/*.db*
# 예: app.db, app.db-wal, app.db-shm
SQLite 내부 상태 조회
CLI로 DB에 붙어서 WAL과 체크포인트 상태를 확인합니다.
sqlite3 ./data/app.db "PRAGMA journal_mode;"
sqlite3 ./data/app.db "PRAGMA wal_checkpoint(PASSIVE);"
sqlite3 ./data/app.db "PRAGMA wal_autocheckpoint;"
sqlite3 ./data/app.db "PRAGMA busy_timeout;"
PRAGMA wal_checkpoint(PASSIVE); 결과는 보통 3개 정수(예: busy, log, checkpointed) 형태로 나오며, busy가 0이 아니거나 log가 계속 큰 상태면 체크포인트가 막히는 상황일 수 있습니다.
장시간 읽기 트랜잭션 탐지(앱 레벨)
AutoGPT는 “대화/태스크 실행 로그를 스트리밍으로 읽으며 동시에 쓰기”가 섞이기 쉽습니다. ORM이나 래퍼가 커넥션을 오래 물고 있으면 체크포인트가 진행되지 못합니다.
- 읽기 트랜잭션을 짧게 유지(필요한 row만 읽고 즉시 커밋/종료)
- 커넥션 풀에서 커넥션이 반납되지 않는지 확인
2단계: 운영에 안전한 WAL 체크포인트 전략
체크포인트는 크게 PASSIVE, FULL, RESTART, TRUNCATE가 있습니다. 일반적으로:
PASSIVE: 다른 커넥션을 방해하지 않지만 진행이 덜 될 수 있음FULL: 더 적극적이지만 경합 시 대기/실패 가능RESTART: WAL을 처음부터 다시 쓰게 유도TRUNCATE: WAL 파일을 잘라 크기를 즉시 줄임(디스크 절약에 유리)
운영에서는 보통 주기적 wal_checkpoint(TRUNCATE) 또는 워크로드에 따라 FULL을 섞는 방식이 효과적입니다.
파이썬에서 주기적 체크포인트 실행 예시
AutoGPT 실행 프로세스 내에서 “백그라운드 유지보수 스레드”로 체크포인트를 돌릴 수 있습니다.
import sqlite3
import threading
import time
DB_PATH = "./data/app.db"
def checkpoint_loop(interval_sec: int = 30):
while True:
try:
conn = sqlite3.connect(DB_PATH, timeout=5)
conn.execute("PRAGMA busy_timeout=5000;")
# 상황에 따라 PASSIVE/FULL/RESTART/TRUNCATE 조합
conn.execute("PRAGMA wal_checkpoint(TRUNCATE);")
conn.close()
except Exception:
# 운영에서는 로깅만 하고 죽지 않게
pass
time.sleep(interval_sec)
threading.Thread(target=checkpoint_loop, daemon=True).start()
포인트는 2가지입니다.
- 체크포인트 전용 커넥션을 따로 만들기(업무 트랜잭션과 분리)
busy_timeout을 충분히 주기(경합 시 바로 실패하지 않게)
wal_autocheckpoint 값을 워크로드에 맞추기
SQLite는 wal_autocheckpoint로 “WAL 프레임 수 기준 자동 체크포인트”를 설정합니다.
PRAGMA wal_autocheckpoint = 1000;
값이 너무 크면 WAL이 커지고, 너무 작으면 체크포인트가 잦아져 쓰기 지연이 늘 수 있습니다. 일반적으로는 “지연보다 안정성(파일 크기 상한)”이 중요한 AutoGPT 운영에서는 작게 시작해서 관측하며 키우는 접근이 안전합니다.
3단계: 캐시·동기화 설정으로 메모리/지연 균형 맞추기
busy_timeout은 사실상 필수
락 경합이 있는 환경에서 기본값(0)에 가깝게 두면, 체크포인트/쓰기 모두가 쉽게 실패하며 재시도가 누적됩니다.
PRAGMA busy_timeout = 5000;
synchronous와 temp_store
synchronous=NORMAL은 WAL에서 흔히 쓰는 타협점입니다.- 임시 테이블/정렬이 많다면
temp_store=MEMORY가 빨라질 수 있지만, 메모리 예산이 작으면 오히려 RSS를 밀어 올릴 수 있습니다.
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA temp_store = MEMORY;
메모리 상한을 더 강하게 잡고 싶다면 temp_store=FILE로 내려서 RSS 증가를 완화할 수 있습니다.
page_size와 mmap_size는 신중하게
mmap_size를 크게 잡으면 읽기 성능이 좋아질 수 있지만, 환경에 따라 RSS가 커 보이거나(매핑이 잡힘), 컨테이너 메모리 제한과 충돌할 수 있습니다. 운영에서는 먼저 체크포인트/트랜잭션 수명부터 잡고, 그 다음에 mmap_size를 만지는 편이 안전합니다.
4단계: DB 파일이 “줄지 않는” 문제와 VACUUM/auto_vacuum
WAL 체크포인트는 “변경 내용을 메인 DB에 반영”할 뿐, DB 파일 자체의 빈 공간을 즉시 OS에 반환하지는 않습니다. 삭제/갱신이 반복되면 DB 파일이 커진 뒤 유지될 수 있습니다.
- 주기적
VACUUM은 파일을 재작성해 크기를 줄입니다(대신 비용 큼). auto_vacuum=INCREMENTAL은 장기 운영에 유리하지만, 초기 설정이 필요합니다.
-- 최초 1회(주의: 설정 후 VACUUM 필요)
PRAGMA auto_vacuum = INCREMENTAL;
VACUUM;
-- 이후 주기적으로 조금씩 회수
PRAGMA incremental_vacuum(2000);
AutoGPT처럼 로그/이벤트 테이블이 계속 쌓였다가 TTL로 지우는 구조라면, INCREMENTAL이 운영 친화적입니다. PostgreSQL에서 bloat를 VACUUM으로 관리하듯이, SQLite도 “공간 회수 정책”을 의식해야 합니다. (PostgreSQL 사례가 궁금하면: PostgreSQL VACUUM 안 돌면 테이블 폭증 해결법)
5단계: 스키마/쿼리 관점에서 체크포인트 압력을 줄이기
체크포인트는 “WAL에 쌓인 변경량”이 많을수록 부담이 커집니다. 즉, 근본적으로는 불필요한 쓰기를 줄이는 게 최선입니다.
(1) 이벤트/로그는 배치 삽입
토큰 단위로 로그를 매번 insert 하면 WAL이 폭발합니다. 일정 단위로 버퍼링 후 배치 insert 하세요.
def insert_logs(conn, rows):
conn.executemany(
"INSERT INTO agent_logs(run_id, ts, level, message) VALUES (?, ?, ?, ?)",
rows,
)
conn.commit()
(2) 인덱스는 필요한 것만
인덱스는 읽기를 빠르게 하지만, 쓰기 시에는 인덱스 갱신이 추가 WAL을 만듭니다. AutoGPT의 “최근 N개 로그 조회” 정도면 아래처럼 최소 인덱스로 시작하세요.
CREATE INDEX IF NOT EXISTS idx_agent_logs_run_ts
ON agent_logs(run_id, ts);
(3) TTL 삭제는 한 번에 다 지우지 말고 청크로
대량 delete는 WAL에 큰 파도를 만듭니다.
DELETE FROM agent_logs
WHERE id IN (
SELECT id FROM agent_logs
WHERE ts < strftime('%s','now') - 86400
LIMIT 5000
);
이 쿼리는 LIMIT으로 삭제량을 제한해 체크포인트 부담을 분산합니다. (본문에서 부등호를 그대로 쓰면 MDX 빌드 에러가 날 수 있어 < 엔티티로 표기했습니다.)
6단계: 컨테이너/서버 운영 팁(“메모리 누수” 오해 줄이기)
파일 시스템 캐시와 RSS를 구분
리눅스에서 SQLite 파일을 많이 읽고 쓰면 페이지 캐시가 커집니다. 이는 “필요하면 회수되는 메모리”인데, 모니터링에서 단순 RSS만 보면 누수처럼 착시가 납니다.
- 컨테이너라면 cgroup 메모리 지표와 함께 확인
- WAL 비대와 함께 나타나는지(디스크 측정)로 교차 검증
단일 writer 원칙에 가깝게
SQLite는 “여러 reader + 한 writer”에 강합니다. AutoGPT 워커를 여러 개 띄워 한 DB를 공유하면 락 경합이 급증합니다.
- 가능하면 워커별 DB 분리
- 또는 중앙 저장소를 PostgreSQL/MySQL로 승격
락 경합/핫스팟은 DB가 달라도 패턴이 유사합니다. 분산락/경합 튜닝 관점은 다음 글도 참고할 만합니다.
권장 설정 프리셋(안전한 시작점)
아래는 “AutoGPT 단일 인스턴스 + 중간 수준 쓰기 + 장시간 운영” 기준의 시작점입니다.
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA busy_timeout = 5000;
PRAGMA wal_autocheckpoint = 1000;
PRAGMA foreign_keys = ON;
그리고 앱 레벨에서 30초~60초 주기의 wal_checkpoint(TRUNCATE)를 추가한 뒤,
- WAL 파일 크기 상한
- p95 쓰기 지연
- 체크포인트 실행 시간
을 보면서 wal_autocheckpoint와 체크포인트 주기를 조정합니다.
트러블슈팅 체크리스트
db-wal이 계속 커진다- 장시간 읽기 트랜잭션이 있는지 확인
- 체크포인트 전용 커넥션 +
busy_timeout적용 - 주기적
wal_checkpoint(TRUNCATE)도입
DB 파일이 계속 커지고 줄지 않는다
auto_vacuum=INCREMENTAL+ 주기적incremental_vacuum도입- TTL delete를 청크 처리
지연이 간헐적으로 튄다
- 체크포인트 주기가 너무 잦지 않은지 확인
- 인덱스 과다/불필요한 업데이트 줄이기
- 워커 다중화로 SQLite를 공유하고 있지 않은지 점검
마무리
AutoGPT 운영에서 “메모리 누수”라고 느끼는 이슈는, 실제로는 SQLite WAL이 누적되며 생기는 체크포인트 지연 + 파일 비대 + 캐시 증가의 합성 효과인 경우가 많습니다.
가장 효과가 큰 순서는 보통 다음과 같습니다.
- 읽기 트랜잭션을 짧게 유지
busy_timeout과 주기적 체크포인트 도입wal_autocheckpoint튜닝- TTL delete 청크화 +
auto_vacuum로 공간 회수
이 4가지만 잡아도 “재시작하면 괜찮아지는” 유형의 누수 착시가 크게 줄고, 장시간 에이전트 실행의 안정성이 눈에 띄게 좋아집니다.