现场是这样的。LangGraph 0.2 用 SqliteSaver 做会话持久化,压测 20 并发跑同一张图,日志里不断出现 sqlite3.OperationalError: database is locked,重试几次后才成功,P99 从 800 毫秒涨到 6 秒,数据库文件在本地磁盘。
中间走过弯路,把「20 个协程共用一个 saver 实例,没有做串行化」当成主因,改完现象没变。
这类报错背后常见的原因有几处,逐个排除就行。SqliteSaver 每个 checkpoint 都开写事务,SQLite 同时只允许一个写者;连接没设 timeout,默认 5 秒后直接抛 locked;没开 WAL 模式,读写互相阻塞。
先看数据库当前的日志模式
python -c "import sqlite3;c=sqlite3.connect('graph.db');print(c.execute('pragma journal_mode').fetchone())"
然后切成 WAL 并设置忙等待超时
python -c "import sqlite3;c=sqlite3.connect('graph.db',timeout=30);print(c.execute('pragma journal_mode=wal').fetchone())"
接着换 AsyncSqliteSaver 并给单实例加锁
python -c "from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver;print(AsyncSqliteSaver)"
等前面都确认过,再用 20 并发重跑同一张图观察 P99
python load_test.py --concurrency 20 --graph chat --duration 60
中间停过一次,把已经改的东西回滚干净,再重新来一遍。
怎么算真的好了,看的是同一批现象。并发 20 时日志不再出现 database is locked,P99 回到 1 秒以内,checkpoint 条数与请求数一致,没有丢状态。
SQLite 适合单机低并发,20 路以上就该换 Postgres saver;WAL 只能缓解读写冲突,写写冲突还是要靠单写者或换存储。这类问题最好一次改到位,反复小改反而容易把原因搅混。
