Delta Channels: 장기 실행 에이전트를 위한 런타임 진화 방식
요약
LangGraph의 새로운 프리미티브인 DeltaChannel은 장기 실행 에이전트의 체크포인트 저장 공간 문제를 해결하기 위해 매 단계 전체 스냅샷 대신 차이점(delta)만을 저장하는 방식입니다. 이를 통해 메시지 히스토리와 파일이 누적됨에 따라 발생하는 O(N²)의 저장 공간 급증 문제를 해결하고, 데이터 크기를 획기적으로 줄이면서도 재개 성능을 유지합니다.
핵심 포인트
- 기존 전체 스냅샷 방식의 O(N²) 저장 공간 증가 문제를 델타 기반 저장 방식으로 해결
- 200턴 실행 코딩 에이전트 기준, 저장 용량을 5.3GB에서 129MB로 약 40배 이상 절감
- K 단계마다 주기적으로 전체 스냅샷을 기록하여 재개 지연 시간(resume latency)을 제한
- LangGraph 1.2의 새로운 프리미티브로, 기존의 중단, 타임 트래블, 툴링 API와 완벽히 호환
핵심 요약
DeltaChannel은
매 단계마다 차이점(delta)만을 저장하고 K 단계마다 주기적으로 전체 스냅샷(full snapshots)을 기록합니다. 이를 통해 세션이 길어짐에 따라 저장 비용을 일정하게 유지하면서도 재개 지연 시간(resume latency)을 제한합니다. messages 및 files는 Deep Agents v0.6에서 기본적으로 델타 기반(delta-backed)으로 작동하며, 전체 LangGraph API 표면(중단(interrupts), 타임 트래블(time-travel), 툴링(tooling))은 변경되지 않습니다. Deep Agents는 에이전트의 진행 상황을 매 단계마다 체크포인트(checkpoint)로 저장하는 LangGraph 런타임(runtime)을 기반으로 구축되었습니다. 이것이 관찰 가능성(observability), 인간 참여형(human-in-the-loop), 그리고 장애 복구(failure recovery)를 가능하게 합니다. 즉, 에이전트가 정확히 어디에 있는지 항상 알 수 있으며 어느 지점에서든 재개할 수 있습니다.
에이전트의 능력이 향상됨에 따라:
- 메시지 히스토리(message histories)가 수십 또는 수백 단계에 걸쳐 증가하며 더 오래 실행됩니다.
- 컨텍스트 관리 및 오프로딩(offloading)을 위해 파일 시스템을 활용하며 더 많은 컨텍스트를 사용합니다.
Deep Agents의 경우, 메시지 히스토리와 파일은 에이전트 상태(agent state)에 존재하며, 매 단계마다 스냅샷을 찍는 방식에서는 체크포인트 저장 공간이 **O(N²)**로 증가합니다. 200턴을 실행하는 코딩 에이전트의 경우, 현재의 체크포인팅 방식은 체크포인터(checkpointer)에 5.3GB를 직렬화(serialize)합니다. 델타 채널(Delta channels)을 사용하면 이를 129MB로 줄일 수 있으며, 이는 40배 이상의 감소이며 상태 재수화(state rehydration) 시 성능 저하는 거의 없습니다.
델타 채널은 이에 발맞추어 런타임을 진화시키는 방식입니다. DeltaChannel은 누적되는 상태 필드(accumulating state fields)가 체크포인트되는 방식을 변경하는 langgraph 1.2의 새로운 프리미티브(primitive)입니다. 매 단계마다 전체 스냅샷을 직렬화하는 대신, 각 단계에서는 차이점(diff)만을 저장합니다. 복구 비용을 제한하기 위해 전체 스냅샷은 주기적으로 기록됩니다. Deep Agents의 경우, 이는 messages와 files에 대한 델타 기반 저장(delta-based storage)을 의미합니다. 에이전트 진행 상황의 전체 히스토리를 그대로 유지하면서도 비용은 아주 적게 들게 됩니다.
LangGraph의 체크포인트 저장 공간은 메시지 히스토리가 긴 에이전트의 경우 O(N²)으로 증가합니다. 200턴을 실행하는 코딩 에이전트의 경우 이는 5.3GB에 달합니다. 델타 채널은 이를 129MB로 줄여주며, 이는 별도의 비용 없이 41배의 감소를 가져옵니다.
문제점: O(N²) 체크포인트 저장 공간
기본 LangGraph 체크포인팅 (checkpointing) 모델은 매 단계마다 에이전트 상태 (agent state)의 전체 스냅샷 (full snapshot)을 기록합니다. 규모가 작고 수명이 짧은 에이전트의 경우에는 문제가 되지 않습니다. 하지만 messages와 files는 *추가 전용 누적기 (append-only accumulators)*로, 데이터가 계속해서 늘어나기만 합니다.
전체 스냅샷 체크포인팅 방식에서는 체크포인트 N이 1단계부터 N단계까지의 모든 내용을 포함하게 됩니다.
이러한 성장은 체크포인트 계층 전반에 걸쳐 복리로 작용합니다. 각 단계는 이전 단계보다 더 많은 데이터를 직렬화 (serialize)하고, 체크포인터 (checkpointer)에 더 큰 블롭 (blob)을 기록하며, 이를 유지하기 위해 더 많은 메모리를 소비합니다. 즉, 직렬화 시간, 쓰기 증폭 (write amplification), 그리고 중복 저장 공간이라는 비용을 지불하게 됩니다.
해결책: 델타 채널 (Delta Channels)
채널 (Channels)은 그래프 상태 (graph state) 내의 "필드 (field)"를 나타내는 데 사용되는 LangGraph의 기본 요소 (primitive)입니다. 서로 다른 채널 유형은 체크포인트를 통해 데이터가 전달되는 방식을 제어합니다.
DeltaChannel은 누적 필드에 대한 체크포인트 표현 방식을 변경하는 새로운 LangGraph 채널 유형입니다 (1.2 버전 기준 베타 단계).
일반적인 단계에서 DeltaChannel은 해당 단계에서 추가된 새로운 업데이트 사항, 즉 아주 작은 델타 (delta) 값만을 기록합니다.
전체 스냅샷은 snapshot_frequency=K 단계마다 기록됩니다 (deepagents의 경우 기본값은 50입니다). 이는 재개 (resume) 시 상태를 재구성하는 비용을 제한합니다. 세션 시작부터 모든 델타 기록을 다시 재생 (replay)하는 대신, 런타임은 가장 가까운 스냅샷까지만 거슬러 올라가면 됩니다. 즉, 최대 K 단계까지만 이동하면 됩니다. 주기적인 스냅샷이 없다면, 매우 긴 세션의 경우 재개 속도가 매우 느려질 것입니다.
기저에 깔린 성장 곡선은 여전히 이차 함수적 (quadratic)이지만 (스냅샷이 K 단계마다 발생하기 때문), 계수는 기준점의 약 1/K 수준입니다. 실제 세션 길이에서는 O(N)의 델타 항이 지배적이며, 재구성 비용이 K에 의해 제한되기 때문에 재개 지연 시간 (resume latency)은 일정하게 유지됩니다. 저장 공간 측면에서의 이득은 사실상 비용 없이 얻을 수 있습니다.
다음은 표준 스냅샷 방식과 델타 방식의 나란한 비교입니다:
벤치마크 결과
DeltaChannel
is LangGraph의 프리미티브 (primitive)이지만, 이를 개발하게 된 동기가 된 워크로드이자 여기서 벤치마킹하고 있는 대상은 Deep Agents 코딩 세션입니다. 긴 메시지 히스토리(message histories)와 파일 시스템 기반의 컨텍스트 오프로드(context offload)는 체크포인트(checkpoint)의 $O(N^2)$ 성장이 실제 운영상의 문제가 되는 정확한 상태 형태(state shape)입니다.
우리는 두 가지 워크로드를 실행했습니다:
주기적인 대규모 검색 결과가 FilesystemMiddleware의 20k 토큰 축출 임계값(eviction threshold)을 초과하여, messages에서 files로 오프로드됩니다.
방법론 (Methodology)
모든 벤치마크는 완전히 모킹(mocked)된 워크로드를 사용합니다. 즉, 실제 LLM 호출이 없으며, InMemorySaver, 결정론적 모크 모델(deterministic mock model)을 사용하여 완전히 재현 가능합니다. 표에 보고된 값은 **총 체크포인터 저장 용량 (total checkpointer storage)**으로, 전체 세션 동안 세이버(saver)에 누적된 모든 바이트를 의미합니다. 토큰 수는 FilesystemMiddleware가 내부적으로 축출 임계값 계산을 위해 사용하는 total_message_chars / 4 근사치를 사용합니다.
설정은 다음과 같습니다:
checkpointer = InMemorySaver()
agent = create_deep_agent(
model=_MockModel(), # 결정론적 모크, API 호출 없음
tools=[external_search],
checkpointer=checkpointer,
)
for i in range(turns):
agent.invoke({"messages": [HumanMessage(...)]}, config)
워크로드 A: 가벼운 코딩 및 검색 (light coding and search)
저장 용량은 처음에는 천천히 증가하다가, 전체 스냅샷(snapshot) 크기가 복리로 증가함에 따라 급격히 가속화됩니다. 500턴(turns) 시점에서 베이스라인(baseline)은 4GB가 누적된 반면, 델타 채널(delta channels)은 110MB 미만을 유지합니다.
절감 비율은 10턴에서 6배에서 500턴에서 41배로 증가합니다. 여전히 상승 중이지만, 이론적인 ~K배 천장에 가까워짐에 따라 감속하고 있습니다. 이 천장은 고정되어 있지 않습니다. snapshot_frequency를 구성할 수 있으므로, 워크로드에 따라 재개 지연 시간(resume latency)과 저장 용량 절감 사이에서 트레이드오프(trade-off)를 할 수 있습니다. K값이 높을수록 세션당 전체 쓰기(full writes) 횟수가 적어지고 저장 용량 감소 폭이 커지지만, 재개 시 델타 재생(delta replay) 비용이 약간 더 증가합니다.
워크로드 B: 다중 파일 코딩 세션 (multi file coding session)
턴당 상태(state)가 더 무거워지면 $O(N^2)$ 곡선이 더 빠르게 가팔라집니다. 베이스라인은 에이전트가 작업하는 현실적인 오후 시간인 단 200턴 만에 5.3GB에 도달합니다.
절감 비율(savings ratio)은 200턴에서 41배에 달하며 계속 상승 중입니다. 두 워크로드(workload) 모두 동일한 ~K× 점근선(asymptote)을 향해 수렴하지만, 더 무거운 워크로드가 더 빠르게 도달합니다. 이는 턴당 더 큰 쓰기(write) 작업이 이차 계수(quadratic coefficient)를 더 공격적으로 증폭시키기 때문입니다.
Workload B의 경우 각 턴 수에서 절감 비율이 일관되게 더 높게 나타나는데, 이는 턴당 더 큰 상태(state)가 $O(N^2)$ 계수를 더 빠르게 증폭시키기 때문입니다. 두 워크로드 모두 동일한 점근선(~snapshot_frequency ×)을 향해 수렴하지만, 더 무거운 워크로드가 더 일찍 도달합니다.
API
Deep Agents에서
deepagents v0.6에서는 Delta channels가 기본적으로 활성화되어 있습니다.
messages와 files 모두 Delta channel을 기반으로 동작합니다. 별도의 설정은 필요하지 않습니다.
LangGraph에서
DeltaChannel은 LangGraph에서 모든 상태 필드(state field)에 사용할 수 있는 일급 프리미티브(first class primitive)입니다.
from typing_extensions import Annotated
from langgraph.channels.delta import DeltaChannel
def append(state: list[str], writes: list[list[str]]) -> list[str]:
...
두 가지 파라미터:
— 배치 불변성(batching-invariant)을 유지해야 하는 순수 함수 reducer (state, list[writes]) -> new_state : reducer(reducer(s, xs), ys) == reducer(s, xs + ys)가 성립해야 합니다. 자세한 리듀서 계약(reducer contract)은 아래를 참조하세요.
— 전체 스냅샷(full snapshot)을 얼마나 자주 작성할지 결정하는 snapshot_frequency (기본값: 1000). 값이 높을수록 세션당 전체 쓰기 횟수는 줄어들지만, 재개(resume) 시 더 많은 델타 재생(delta replay)이 필요합니다. deepagents는 50을 사용합니다.
이것이 API 표면(API surface)의 전체 변경 사항입니다. 기존 도구, 인터럽트 처리(interrupt handling), 타임 트래블(time-travel) 기능은 모두 그대로 작동합니다.
리듀서 계약: 폴드(folds) 간의 결합 법칙(associativity)
DeltaChannel은 기존의 BinaryOperatorAggregate 채널보다 리듀서에 더 엄격한 요구 사항을 부과합니다. 이는 직접 델타 기반 상태를 정의할 때 반드시 올바르게 구현해야 하는 유일한 사항입니다.
기존 계약
def reducer(existing: T, update: T) -> T: ...
새로운 계약
# 배치 폴드(Batch fold) — 축적된 모든 쓰기 작업을 한 번에 호출함
def reducer(state: T, writes: list[T]) -> T: ...
DeltaChannel
DeltaChannel은 마지막 로드 이후 누적된 모든 쓰기(writes)를 단 한 번의 호출로 전달합니다. 재구성된 결과는 해당 쓰기들이 어떻게 배치(batch)되느냐에 관계없이 반드시 동일해야 합니다:
reducer(reducer(state, [w1, w2]), [w3, w4]) == reducer(state, [w1, w2, w3, w4])
이를 **배치 불변성 (batching-invariance)**이라고 합니다. 만약 리듀서(reducer)가 이를 위반하면, 델타 채널(delta channel) 상태가 전체 스냅샷(full snapshot)과 조용히 어긋나게 되며, 이는 스냅샷 경계에 걸쳐 있는 세션에서만 발생합니다.
델타 적용 전 스레드에서의 마이그레이션 (Migration from pre-delta threads)
데이터 마이그레이션은 필요하지 않습니다. DeltaChannel.from_checkpoint가 일반 상태 값(plain state value, _DeltaSnapshot이 아닌 경우)을 만나면, 이를 베이스 상태(base state)로 직접 사용합니다. 기존 스레드는 계속 작동하며, 업그레이드 후 생성되는 첫 번째 새 체크포인트는 해당 일반 값 시드(plain-value seed) 위에 델타(deltas)를 쓰기 시작합니다.
다음 단계
델타 채널은 deepagents v0.6 및 langgraph v1.2에 포함되어 출시됩니다. 업그레이드 경로는 원활할 것입니다.
델타 채널과 관련된 이점은 세션이 길어질수록 복리로 증가합니다. 깊은 컨텍스트(deep context)를 가진 장기 실행 에이전트(long-running agents)는 이 분야가 나아가는 방향이며, 델타 채널은 우리의 런타임(runtime)이 그 요구 사항을 충족하기 위해 확장하는 방식입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 LangChain Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기