영속성 (Persistence)
영속성 (Persistence)
영속성(Persistence)은 LangGraph 애플리케이션이 단 한 번의 그래프 실행(graph run)을 넘어서도 유용한 정보를 보관할 수 있게 해줘요. 이 기능이 꼭 필요한 순간이 있는데요, 에이전트가 대화를 이어가야 하거나, 중단(interruption) 이후 다시 재개해야 하거나, 실패에서 복구해야 하거나, 여러 상호작용에 걸쳐 정보를 기억해야 하는 경우가 바로 그런 상황이에요.
LangGraph는 이 문제를 해결하기 위해 서로 보완해 주는 두 가지 영속성 시스템을 제공합니다.
- 체크포인터 (Checkpointers): 스레드의 그래프 상태를 체크포인트(checkpoint)로 저장해요. 단기적이고 스레드 범위에 한정된 메모리용으로 사용하면 되는데, 대화의 연속성, 인간-인-더-루프(human-in-the-loop) 워크플로, 타임 트래블(time travel), 장애 허용(fault tolerance) 등이 여기에 해당해요.
- 스토어 (Stores): 그래프 상태 밖에 애플리케이션이 정의한 데이터를 저장해요. 장기적이고 스레드를 넘나드는 메모리용으로 사용하면 되는데, 사용자 선호, 사실, 공유 지식 등이 여기에 해당해요.
대부분의 애플리케이션은 두 시스템을 함께 쓰게 돼요. 체크포인터가 현재 스레드를 추적하고, 스토어가 스레드를 아우르는 지속적인 정보를 추적하니까요.
퀵스타트 (Quickstart)
그래프를 컴파일할 때 체크포인터, 스토어, 또는 둘 다를 함께 전달하면 됩니다.
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
checkpointer = InMemorySaver()
store = InMemoryStore()
graph = builder.compile(checkpointer=checkpointer, store=store)
result = graph.invoke(
{"messages": [{"role": "user", "content": "Hi, my name is Bob."}]},
{"configurable": {"thread_id": "thread-1"}},
)
에이전트 서버(Agent Server)는 영속성을 자동으로 처리해요. 에이전트 서버를 사용한다면 체크포인터나 스토어를 직접 구현하거나 설정할 필요가 없어요. 서버가 뒤에서 영속성 인프라를 대신 처리해 주거든요.
체크포인터 vs. 스토어
| 체크포인터 (Checkpointer) | 스토어 (Store) | |
|---|---|---|
| 저장 내용 | 그래프 상태의 스냅샷 | 애플리케이션에서 정의한 키-값 데이터 |
| 범위 (Scope) | 단일 스레드 | 스레드 간 (크로스 스레드) |
| 메모리 유형 | 단기, 스레드 범위 메모리 | 장기, 크로스 스레드 메모리 |
| 용도 | 대화 연속성, 인간-인-더-루프, 타임 트래블, 장애 허용 | 사용자 선호, 사실, 공유 지식 |
| 접근 방식 | 그래프 config에 thread_id 전달 |
노드 또는 애플리케이션 코드에서 항목 읽기/쓰기 |
| 전체 가이드 | 체크포인터 | 스토어 |
흔한 문제 해결
PostgresSaver: thread_id가 너무 긴 경우
PostgresSaver(또는 AsyncPostgresSaver)를 사용할 때 thread_id는 길이가 제한된 컬럼에 저장됩니다. thread_id가 컬럼 크기를 넘어서면 데이터베이스 오류가 나게 되는데요. 해결책: thread_id 값을 255자 미만으로 유지하세요. 결정적인(선택 가능한) ID가 필요하다면 UUID나 해시를 사용하면 됩니다.
import uuid
config = {"configurable": {"thread_id": str(uuid.uuid4())[:255]}}
MemorySaver는 재시작 후에도 유지되지 않아요
MemorySaver와 InMemorySaver는 체크포인트를 RAM에 저장합니다. 프로세스가 재시작되면 모든 체크포인트가 사라지는데요. 해결책: 운영 환경(프로덕션)에서는 지속형 체크포인터를 사용하세요.
PostgresSaver: 비동기 지원이 되는 PostgreSQLSqliteSaver: 개발용 로컬 파일 기반 저장소
체크포인트가 끝없이 늘어나는 경우
대화가 길어지면 체크포인트가 계속 쌓입니다. 이렇게 되면 지연 시간(latency)과 저장 비용이 늘어날 수 있는데요. 해결책: 오래된 체크포인트를 주기적으로 정리(prune)하거나 보존 정책(retention policy)을 설정하세요.
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://...")
checkpointer.setup() # 인덱스가 있는 테이블을 생성합니다
# N일보다 오래된 체크포인트를 삭제하는 cron 작업을 추가하는 것을 고려해 보세요
부모 그래프에서 서브그래프로의 상태 접근
서브그래프가 상태를 업데이트해도 부모 그래프가 즉시 그 변경을 보지 못할 수 있어요. 이유는 각 서브그래프가 **자체 체크포인트 네임스페이스(checkpoint namespace)**를 관리하기 때문입니다. 해결책: 그래프 경계를 넘어야 하는 데이터에는 스토어를 통한 공유 상태를 사용하거나, 서브그래프가 부모 체크포인트에 쓰도록 구성하세요.
다음 단계
- 체크포인터 사용하기 — 스레드 상태를 저장하고 검사하기.
- 스토어 사용하기 — 스레드를 아우르는 지속 데이터를 저장하기.