langchain-ai / langchain-ai/langgraph

perf(checkpoint-sqlite): N+1 query pattern in SqliteSaver.list() and AsyncSqliteSaver.alist()

Open
#7,263 3 comments 0 reactions 0 assignees View on GitHub
bug external
Dominant language
Python
Stars
41.8k
Forks
7.1k
Avg merge
23h 7m
Merged PRs (30d)
30

Description

### Checked other resources

- [x] This is a bug, not a usage question.
- [x] I added a clear and descriptive title that summarizes this issue.
- [x] I used the GitHub search to find a similar question and didn't find it.
- [x] I am sure that this is a bug in LangGraph rather than my code.
- [x] The bug is not resolved by updating to the latest stable version of LangGraph (or the specific integration package).
- [x] This is not related to the langchain-community package.
- [x] I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

### Related Issues / PRs

#7204

### Reproduction Steps / Example Code (Python)

```python
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver

conn = sqlite3.connect(":memory:", check_same_thread=False)
saver = SqliteSaver(conn)
saver.setup()

# Insert 100 checkpoints with writes
for i in range(100):
checkpoint_id = f"{i:032}.0"
config = {"configurable": {"thread_id": "1", "checkpoint_ns": "", "checkpoint_id": checkpoint_id}}
conn.execute(
"INSERT INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, type, checkpoint, metadata) VALUES (?, ?, ?, ?, ?, ?)",
("1", "", checkpoint_id, "json", b'{}', b'{}'),
)
conn.execute(
"INSERT INTO writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
("1", "", checkpoint_id, "task1", 0, "messages", "json", b'[]'),
)
conn.commit()

# This triggers 101 queries (1 for checkpoints + 100 for writes)
# Enable SQLite tracing to observe:
queries = []
conn.set_trace_callback(lambda q: queries.append(q))
list(saver.list({"configurable": {"thread_id": "1"}}))
print(f"Total queries executed: {len(queries)}")
# Output: Total queries executed: 101
# Expected: 2
```

### Error Message and Stack Trace (if applicable)

```shell

```

### Description

Description:
`SqliteSaver.list()` and `AsyncSqliteSaver.alist()` execute a separate
writes query for every checkpoint row returned. This is a classic N+1
query pattern.

- I'm listing checkpoints via `SqliteSaver.list()` on a thread with N checkpoints.
- I expect the number of database queries to be constant (2: one for checkpoints, one for all writes).
- Instead, it executes N+1 queries (1 for checkpoints + 1 per checkpoint for writes), which degrades linearly.

**Affected files:**
- `libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py` — `SqliteSaver.list()` (line 335)
- `libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/aio.py` — `AsyncSqliteSaver.alist()` (line 431)

### System Info

> OS: Darwin
> OS Version: Darwin Kernel Version 24.6.0
> Python Version: 3.14.0

> langchain_core: 1.2.11
> langsmith: 0.6.4
> httpx: 0.28.1
> orjson: 3.11.5
> pydantic: 2.12.5

Contributor guide

Open the contributing guide

Research direction

Read SqliteSaver.list() in libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py and AsyncSqliteSaver.alist() in libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/aio.py. Run the provided reproduction with SQLite tracing, then verify both methods handle all checkpoint writes with a constant two-query count rather than one query per checkpoint.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, sqlite
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.