mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-21 17:18:09 +02:00
refactor(delta-channel): drop snapshot_every and saver Overwrite terminator
snapshot_every was a knob for bounding reconstruction cost on deep threads. Benchmarks (notes/add_messages_replay_problem.md + scratch work on sr/add-messages-replay-bench) showed the add_messages fast-path (optimize/add-messages-fast-path) closes the quadratic replay cost for threads under ~1000 turns, where the crossover to snapshots makes sense. For deeper threads we'll ship a first-class compaction primitive instead. Removals: * DeltaChannel: snapshot_every ctor param, _writes_since_snapshot counter, should_snapshot() / snapshot_write() methods, counter threading through _apply_write / update / from_checkpoint / copy. * Pregel loop: post-checkpoint snapshot-injection block and SNAPSHOT_TASK_ID import + constant. * Checkpoint base: _overwrite_types() helper and the ancestor-walk short-circuit on user-emitted Overwrite in sync + async get_channel_writes. * InMemory + Postgres savers: same walk-terminator shortcut. The pre-delta blob terminator (seed-from-ancestor-blob) stays — it's required for migration correctness, not a snapshot optimization. * Tests for all of the above. Preserved: * Channel-level Overwrite semantics in DeltaChannel / BinOpAggregate: Overwrite still resets the value at reducer level; same-super-step dedup and InvalidUpdateError on multiple Overwrites still enforced. * Pre-delta migration seeding.
This commit is contained in:
@@ -13,7 +13,6 @@ from langgraph.checkpoint.base import (
|
||||
BaseCheckpointSaver,
|
||||
ChannelVersions,
|
||||
DeltaChannelWrites,
|
||||
_overwrite_types,
|
||||
get_checkpoint_id,
|
||||
)
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
@@ -238,19 +237,14 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
||||
(`AsyncPostgresSaver`); both paths run the queries themselves and
|
||||
feed the rows here.
|
||||
|
||||
Walk is newest → oldest from the target's parent. Stops at the first
|
||||
terminator:
|
||||
|
||||
* a user-emitted `Overwrite` in `checkpoint_writes` — replaces
|
||||
prior history;
|
||||
* a non-sentinel blob in `checkpoint_blobs` — a pre-delta snapshot;
|
||||
bound as `DeltaChannelWrites.seed` so replay starts from it.
|
||||
Walk is newest → oldest from the target's parent. A non-sentinel
|
||||
blob in `checkpoint_blobs` (a pre-delta snapshot) terminates the
|
||||
walk and is bound as `DeltaChannelWrites.seed` so replay starts
|
||||
from it.
|
||||
|
||||
Writes stored at `target_id` itself are pending writes for the next
|
||||
step and are excluded — the walk begins at the target's parent.
|
||||
"""
|
||||
overwrite_types = _overwrite_types()
|
||||
|
||||
parent_of: dict[str, str | None] = {}
|
||||
ver_of: dict[str, str | None] = {}
|
||||
for r in parents_rows:
|
||||
@@ -298,15 +292,9 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
||||
seed = blob_value
|
||||
found_seed = True
|
||||
break
|
||||
terminated = False
|
||||
for type_tag, write_blob, _task_id, _idx in writes_by_cid.get(cid, []):
|
||||
val = self.serde.loads_typed((type_tag, write_blob))
|
||||
collected.append(val)
|
||||
if isinstance(val, overwrite_types):
|
||||
terminated = True
|
||||
break
|
||||
if terminated:
|
||||
break
|
||||
|
||||
collected.reverse() # oldest → newest
|
||||
if found_seed:
|
||||
|
||||
Reference in New Issue
Block a user