langchain-ai / langchain-ai/langgraph
perf(checkpoint-sqlite): N+1 query pattern in SqliteSaver.list() and AsyncSqliteSaver.alist()
- 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
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