Skip to content

perf(wallet): join the transaction lookups only when a column filter needs them - #240

Merged
argszero merged 1 commit into
mainfrom
fix/tx-summary-joins-only-when-needed
Sep 14, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/tx-summary-joins-only-when-needed

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

修复宿主报告(rant 2026-09-14T21:0x):dev 上刷新 #/sharing停在登录页(URL 仍 #/sharing、token 仍在 sessionStorage)。

根因/api/transactions 的 summary / COUNT / trend 三条语句无条件拼上 tx_joins()(keys/users/api_keys 三个 LEFT JOIN),而这几个 LEFT JOIN 一出现,SQLite 就弃用覆盖索引、退回逐行回表。同一条聚合语句、同一个 dev 库(18.6 万行,NFS)实测:

形态 计划 耗时
不带 JOIN COVERING INDEX idx_transactions_user_id_time_type_pts_tokens 0.21s
tx_joins() SEARCH t USING INDEX idx_transactions_user_id_id 22.6s

只有 user_name / key_name 两个筛选会引用 JOIN 表列(model / status / pts 全在 transactions 自身)⇒ 三条「只碰 t.*」的语句改为按需 JOIN

故障链(browser-harness 实测):慢查询长时间占住共享 DB 锁 → /api/me/api/models 被排队(实测单次 /api/me 曾被卡到 35s,并发下出现网关 504)→ ui/js/app.js 的 boot 会话恢复串行等 4 个请求全部完成enterApp(),其中 /api/models 是非必需的 → 任一个卡住,登录页就无限期停着(且 api.js 无任何超时)。实测 3 次刷新中 2 次卡住。

Related Issue

保留 token 却显示登录页是误导性最强的表现,故前端错误兜底另开一条(见 rant 正文第 4 点),本 PR 只修慢查询这一层。

Changes

  • src/routes/wallet.rs:新增 needs_joins()(唯一判定谓词)与 tx_joins_if();summary / COUNT / trend 按需 JOIN
  • 列表查询保持 JOIN(要渲染 user_name / key_label / key_name,且有 LIMIT 兜底)
  • 无配置/数据结构改动(无需同步示例文件)

Tests

  • cargo test 249 passed / 0 failed
  • cargo fmt --check 通过
  • 既有 user_name / key_name 筛选测试覆盖 JOIN 路径仍在(transactions_user_and_api_key_name 等)
  • 验收证据=上面的 EXPLAIN QUERY PLAN + 同副本 A/B 计时(0.21s vs 22.6s)

Checklist

  • 分支命名符合约定(fix/
  • Commit message 使用 Conventional Commits 格式
  • 单一职责、改动最小(1 文件,32+/3-)

…needs them

Host report (rant 2026-09-14T21:0x): refreshing `#/sharing` on dev lands on the
login page. Measured with browser-harness: `/api/me` and `/api/models` are
queued behind slow queries on a shared DB lock, and the boot session restore
awaits all four requests before calling `enterApp()` — so the login page stays
up (with the token intact) until the browser eventually retries.

The slow query is `/api/transactions`: its summary, COUNT and trend statements
add `tx_joins()` (keys/users/api_keys) unconditionally, and the presence of
those LEFT JOINs makes SQLite drop the covering index for a row-by-row lookup.

Same statement, dev db (186k rows on NFS), measured on one copy:

    no joins   -> COVERING INDEX idx_transactions_user_id_time_type_pts_tokens   0.21s
    tx_joins() -> SEARCH ... USING INDEX idx_transactions_user_id_id            22.6s

Only `user_name` and `key_name` filters ever reference the joined tables:
`model`, `status` and the pts range all live on `transactions` itself. So the
three statements that touch nothing but `t.*` now join only when one of those
two filters is present. `needs_joins()` is the single predicate for that, so
the JOIN and the references in `tx_where` cannot fork again.

The list query keeps its joins (it renders user_name / key_label / key_name);
it is bounded by `ORDER BY t.id DESC LIMIT n`, so it stays cheap.

`cargo test` 249 passed / 0 failed; `cargo fmt --check` clean. The SQL-plan
comparison above is the acceptance evidence; the existing filter tests cover
the joined paths (user_name / key_name still filter correctly).
@argszero
argszero merged commit 95d69ba into main Sep 14, 2026
1 check passed
@argszero
argszero deleted the fix/tx-summary-joins-only-when-needed branch September 14, 2026 13:59
@argszero argszero mentioned this pull request Sep 14, 2026
9 tasks
argszero added a commit that referenced this pull request Sep 14, 2026
Ships two changes since v0.7.23 (#239, #240):

- #240: the transactions summary/COUNT/trend statements joined keys/users/
  api_keys unconditionally, which made SQLite drop the covering index and
  fall back to a row-by-row lookup (0.21s -> 22.6s on the dev db, 186k rows
  over NFS). That query held the shared DB lock long enough to queue
  /api/me and /api/models behind it, and the boot session restore waits for
  all four requests, so a refresh could sit on the login page with the token
  intact. The joins are now conditional on a column filter actually needing
  them.
- #239: README tagline rewritten and the live instance surfaced.

No schema change (12 -> 14 happened in v0.7.23).
- Cargo.toml / Cargo.lock: 0.7.23 -> 0.7.24.
- ui/index.html: asset cache-bust -> 20260914-11 (all five refs).
- CHANGELOG.md: v0.7.24 entry.

Gates: `cargo test` 249 passed / 0 failed, `cargo fmt --check` clean.
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.

1 participant