Skip to content

perf(db): SQLite multi-reader connection pool, pragmas via DSN - #113

Open
suantea wants to merge 2 commits into
PIKACHUIM:mainfrom
suantea:perf/db-conn-pool
Open

suantea wants to merge 2 commits into
PIKACHUIM:mainfrom
suantea:perf/db-conn-pool

Conversation

@suantea

@suantea suantea commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

概述

SQLite 连接池从单连接改为多读单写,让 WAL 真正发挥并发读能力;pragma 改经 DSN 下发,确保池内每个连接都生效。

改动

  • 连接池 4~8 连接(按 CPU 数),API 读与监控写入不再互相排队
  • busy_timeout(5000) 兜底写锁等待,避免 BUSY 报错
  • WAL / synchronous / foreign_keys / cache_size 全部改由 DSN 传入(原 db.Exec 方式只作用于单个连接,池化后其余连接不生效)
  • cache_size 16MB 降低磁盘 IO

验证

  • go build / go vet / 全量 go test ./... 通过

pikachuren

This comment was marked as outdated.

@suantea

suantea commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

分支已 restack 到当前 main,请重评

按维护者建议(#87 上「每个 PR 都基于 main 重新创建,PR 之间的修改不要重叠」),本分支已重建。

问题背景:本分支此前的 merge-base 停在 dc2877b(#80 合并点),且 #113–#118 这几个 commit 同时挂在多条分支上,导致每个 PR 的 GitHub diff 被放大成"巨型回退补丁"(例如 #121 显示 27 文件、#110 显示 62 文件),评审看到的是累积 diff 而非本 PR 的真实增量。

本次变更:仅把本分支自身的 commit cherry-pick 到当前 main,内容零改动。

  • 旧 head:740cedd
  • 新 head:939f4ba

已验证:go build ./... 通过;本分支自身 patch 与 restack 前逐字节一致(git diff 校验和相同)。

请在新 head 上重评,此时的 diff 只包含本 PR 自己的改动。

@pikachuren pikachuren left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @suantea 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。
⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出是否合并的决策。

🔄 增量评审:上轮 → 本轮

本轮无新增内容改动 —— 分支被 rebase 到 #85 合入后的 main(dc2877b → 26c4301)。已核对 git diff 740cedd5 939f4bad -- backend/model/db.go 为空,db.go 与上轮评审时逐字一致。

新增改动的问题:无(纯 rebase)。

旧问题解决情况:

  • ⏳ [原 P1] MaxOpenConns(8) 放开后,「事务内先读后写」在并发窗口会立刻返回 SQLITE_BUSY_SNAPSHOT(错误码 517),busy_timeout 对此无效;旧配置 MaxOpenConns(1) 由 Go 连接池天然串行化,反而不存在这个问题。
    → 本 PR 内未修复。但 #121 的最新提交正是这个问题的修复(fix(db): SQLite 多连接下事务内先读后写命中 BUSY_SNAPSHOT — 改用 BEGIN IMMEDIATE)。
    → 问题在于:这个修复挂在 #121 的分支上,而 #121 整条链仍是 rebase 前的旧版本(其历史里是 740cedd 这份旧 db.go)。结果就是「修复没跟修复对象放在一起」。
    → 建议二选一:(a) 把 BEGIN IMMEDIATE 的修复单独提一个基于当前 main 的 PR;(b) 直接并入本 PR,让 #113 能独立合并。

🎯 结论:🔄 Request Changes — 内容未变(纯 rebase),原 P1(BUSY_SNAPSHOT)仍待修复,且修复目前挂在了错误的分支上

@pikachuren pikachuren left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @suantea 提交!
🤖 AI 自动审核声明。⚠️ 最终合并决策由项目维护者判定。

🎯 结论

✅ Approve — 改动聚焦正确,是本批 PR 中最干净的一个

📖 概要

SQLite 多读者连接池优化,pragma 通过 DSN 设置
核心改动:MaxOpenConns(1) → 4-8(按 CPU 核数),db.Exec("PRAGMA ...") → DSN query string 参数

🔍 关键验证点

  • WAL 模式 + 多连接组合:自洽正确(多读单写,busy_timeout=5000 兜底写锁等待)
  • 原 db.Exec("PRAGMA journal_mode=WAL") 只对单条连接生效,改 DSN 后所有连接都能应用,修复了一个真实但隐蔽的 bug
  • 连接池参数(MaxOpen 4-8,MaxIdle 4,MaxLifetime 1h)合理保守

P2:缺少并发读验证测试,建议后续补充。合并顺序建议放在 #110/#114 之前,使后者的批量删除/日志写入能从连接池优化中获益。

🎯 结论:✅ Approve — 改动正确、克制、有价值,可直接合并

- 连接池从单连接改为 4~8 连接(按 CPU 数),WAL 并发读真正生效,API 读写不再互相排队
- busy_timeout(5000) 兜底写锁等待,避免 BUSY 报错
- pragma(WAL/synchronous/foreign_keys/cache_size)改由 DSN 传入,确保每个池化连接都生效(原 db.Exec 只作用于单一连接)
- cache_size 16MB 降低磁盘 IO
多连接池下默认 deferred 事务「先读、用到写时才加写锁」,与第三方并发写入
会立刻返回 SQLITE_BUSY_SNAPSHOT(错误码 517),busy_timeout 只对等待写锁
有效、对快照冲突无效,会以 500 暴露给用户。旧实现 MaxOpenConns(1) 由连接
池天然串行化所以没这问题。

- DSN 增加 _txlock=immediate:所有写事务统一用 BEGIN IMMEDIATE 开始,
  一开始就抢占写锁,读-写窗口内不会再有第三方提交写入;拿不到锁时
  busy_timeout 会等待。只读事务不受影响
- 连接数口径改用 GOMAXPROCS(0)(容器里 NumCPU 虚高),下限 2、上限 8
- 空闲连接数降到 2 并设 5 分钟回收,避免小内存设备上 8×16MB 缓存吃内存

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants