perf: optimize portfolio queries and workflows

This commit is contained in:
陈煜 committed 2026-10-02 12:44:07 +08:00
1 parent da98d02661
commit a7e0a0fe58
45 files changed
+4446 -637

No files matched your search

+2
View File
@@ -1,5 +1,7 @@
# WorthPath 首版设计
2026-10-02 更新:查询、历史分页、趋势重放、借入借出联动及三语言的当前实现和实测见 [性能与联动验收](performance.md)。以下首版设计中的历史 ZIP v3/v4 说明保留作格式沿革;当前导出为 v6,兼容读取 v3/v4/v5/v6 ZIP 和旧 JSON。
NestJS API、React/Vite 前端和 MySQL;同源 /api,开发环境由 Vite 代理。
用户拥有持有项目 Position(account/asset/debt)、汇率及登录会话。账户独立保留 asset/liability 属性;信用卡与贷款固定为负债。债务可链接到多个同用户项目,链接不参与求和。每个金额均为非负原币余额,负债按 side 减去。
+405
View File
@@ -0,0 +1,405 @@
{
"tableCount": 8,
"columnCount": 73,
"tables": [
{
"TABLE_NAME": "exchangerate",
"TABLE_COMMENT": "用户原币兑本位币的历史汇率"
},
{
"TABLE_NAME": "icon",
"TABLE_COMMENT": "可复用账户图标"
},
{
"TABLE_NAME": "position",
"TABLE_COMMENT": "持有项目:账户、独立资产和借入借出债务"
},
{
"TABLE_NAME": "positionlink",
"TABLE_COMMENT": "项目关联关系,不参与金额求和"
},
{
"TABLE_NAME": "revision",
"TABLE_COMMENT": "余额历史:金额为变更后的绝对余额"
},
{
"TABLE_NAME": "session",
"TABLE_COMMENT": "登录会话与隐藏项目查看授权"
},
{
"TABLE_NAME": "transfer",
"TABLE_COMMENT": "资金往来及账户与债务的双边配对记账"
},
{
"TABLE_NAME": "user",
"TABLE_COMMENT": "用户及个人设置"
}
],
"columns": [
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "userId",
"COLUMN_COMMENT": "所属用户标识,用于数据隔离"
},
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "currency",
"COLUMN_COMMENT": "原始币种代码"
},
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "baseCurrency",
"COLUMN_COMMENT": "本位币代码"
},
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "date",
"COLUMN_COMMENT": "汇率业务日期"
},
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "rate",
"COLUMN_COMMENT": "每单位原币对应的本位币金额"
},
{
"TABLE_NAME": "exchangerate",
"COLUMN_NAME": "source",
"COLUMN_COMMENT": "汇率来源标识,保留已有历史来源"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "ownerId",
"COLUMN_COMMENT": "上传用户标识;空可表示系统图标或原用户已删除"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "name",
"COLUMN_COMMENT": "显示名称"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "shared",
"COLUMN_COMMENT": "是否公开共享给已登录用户"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "hash",
"COLUMN_COMMENT": "规范化图标内容的 SHA-256 摘要"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "data",
"COLUMN_COMMENT": "规范化静态 PNG 图片二进制"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "source",
"COLUMN_COMMENT": "可选公开来源网址"
},
{
"TABLE_NAME": "icon",
"COLUMN_NAME": "createdAt",
"COLUMN_COMMENT": "记录创建时间,UTC"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "userId",
"COLUMN_COMMENT": "所属用户标识,用于数据隔离"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "kind",
"COLUMN_COMMENT": "项目类型:account 账户、asset 独立资产、debt 债务"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "side",
"COLUMN_COMMENT": "统计方向:asset 资产或借出应收、liability 负债或借入应付"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "name",
"COLUMN_COMMENT": "显示名称"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "category",
"COLUMN_COMMENT": "项目分类代码"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "currency",
"COLUMN_COMMENT": "原始币种代码"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "notes",
"COLUMN_COMMENT": "用户备注,原文保留"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "archived",
"COLUMN_COMMENT": "是否归档;归档项目仍计入总额"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "createdAt",
"COLUMN_COMMENT": "记录创建时间,UTC"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "updatedAt",
"COLUMN_COMMENT": "记录更新时间,UTC"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "importedFromId",
"COLUMN_COMMENT": "备份导入来源项目标识,用于重复导入识别"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "hidden",
"COLUMN_COMMENT": "是否隐藏;未解锁时不参与可见范围统计"
},
{
"TABLE_NAME": "position",
"COLUMN_NAME": "iconId",
"COLUMN_COMMENT": "关联账户图标标识"
},
{
"TABLE_NAME": "positionlink",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "positionlink",
"COLUMN_NAME": "sourceId",
"COLUMN_COMMENT": "关联来源项目标识,通常为债务"
},
{
"TABLE_NAME": "positionlink",
"COLUMN_NAME": "targetId",
"COLUMN_COMMENT": "关联目标项目标识,通常为账户或资产"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "positionId",
"COLUMN_COMMENT": "所属持有项目标识"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "amount",
"COLUMN_COMMENT": "变更后的原币绝对余额,不是收支增量,禁止对全部历史求和"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "effectiveDate",
"COLUMN_COMMENT": "北京时间业务时间对应的 UTC,精确到分钟"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "notes",
"COLUMN_COMMENT": "用户备注,原文保留"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "reason",
"COLUMN_COMMENT": "变更原因:初始、余额、估值、转账或借贷配对操作"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "createdAt",
"COLUMN_COMMENT": "记录创建时间,UTC"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "updatedAt",
"COLUMN_COMMENT": "记录更新时间,UTC"
},
{
"TABLE_NAME": "revision",
"COLUMN_NAME": "sequence",
"COLUMN_COMMENT": "数据库自增唯一顺序,同业务时间按此排序"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "随机会话令牌的 SHA-256 摘要"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "userId",
"COLUMN_COMMENT": "所属用户标识,用于数据隔离"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "expiresAt",
"COLUMN_COMMENT": "会话过期时间,UTC"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "lastActivity",
"COLUMN_COMMENT": "最后有效交互时间,UTC"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "revealUntil",
"COLUMN_COMMENT": "隐藏项目授权截止时间,UTC;空表示未解锁"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "backupDigest",
"COLUMN_COMMENT": "已下载备份内容的 SHA-256 摘要"
},
{
"TABLE_NAME": "session",
"COLUMN_NAME": "backupExpiresAt",
"COLUMN_COMMENT": "备份清空确认凭据截止时间,UTC"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "importedFromId",
"COLUMN_COMMENT": "备份导入来源项目标识,用于重复导入识别"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "userId",
"COLUMN_COMMENT": "所属用户标识,用于数据隔离"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "sourceId",
"COLUMN_COMMENT": "来源资产账户标识"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "targetId",
"COLUMN_COMMENT": "目标账户或债务标识"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "sourceRevisionId",
"COLUMN_COMMENT": "来源账户配对余额历史标识"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "targetRevisionId",
"COLUMN_COMMENT": "目标项目配对余额历史标识"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "sourceCurrency",
"COLUMN_COMMENT": "来源账户币种"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "targetCurrency",
"COLUMN_COMMENT": "目标项目币种"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "amount",
"COLUMN_COMMENT": "本次来源账户本金金额,不是变更后余额"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "received",
"COLUMN_COMMENT": "本次目标到账金额或债务本金金额"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "fee",
"COLUMN_COMMENT": "来源币种手续费;负数表示优惠"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "effectiveDate",
"COLUMN_COMMENT": "北京时间业务时间对应的 UTC,精确到分钟"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "notes",
"COLUMN_COMMENT": "用户备注,原文保留"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "createdAt",
"COLUMN_COMMENT": "记录创建时间,UTC"
},
{
"TABLE_NAME": "transfer",
"COLUMN_NAME": "operation",
"COLUMN_COMMENT": "操作类型:transfer 转账、borrow 借入、lend 借出、collect 收款、repay 还款"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "id",
"COLUMN_COMMENT": "唯一标识"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "username",
"COLUMN_COMMENT": "登录用户名,唯一"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "passwordHash",
"COLUMN_COMMENT": "登录密码的 bcrypt 哈希,不存储明文"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "baseCurrency",
"COLUMN_COMMENT": "本位币代码"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "createdAt",
"COLUMN_COMMENT": "记录创建时间,UTC"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "updatedAt",
"COLUMN_COMMENT": "记录更新时间,UTC"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "idleMinutes",
"COLUMN_COMMENT": "无操作退出分钟数,0 表示关闭"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "hiddenMenus",
"COLUMN_COMMENT": "隐藏菜单标识列表"
},
{
"TABLE_NAME": "user",
"COLUMN_NAME": "showNotes",
"COLUMN_COMMENT": "是否显示备注"
}
]
}
+366
View File
@@ -0,0 +1,366 @@
{
"phase": "final",
"node": "v24.21.0",
"results": [
{
"accounts": 1,
"revisionsPerAccount": 10000,
"baseline": [
{
"name": "old positions",
"medianMs": 182.84,
"bytes": 6097025,
"queries": 5
},
{
"name": "old overview",
"medianMs": 949.27,
"bytes": 38903,
"queries": 4
}
],
"optimized": [
{
"name": "new positions",
"medianMs": 2.67,
"bytes": 368,
"queries": 5
},
{
"name": "new current overview",
"medianMs": 2.42,
"bytes": 316,
"queries": 5
},
{
"name": "new history page",
"medianMs": 21.32,
"bytes": 16462,
"queries": 5
},
{
"name": "new trend",
"medianMs": 51.91,
"bytes": 13485,
"queries": 7
}
],
"httpResults": [
{
"name": "HTTP positions",
"medianMs": 11.59,
"bytes": 368,
"queries": 0
},
{
"name": "HTTP overview",
"medianMs": 7.36,
"bytes": 354,
"queries": 0
},
{
"name": "HTTP history",
"medianMs": 19.2,
"bytes": 16462,
"queries": 0
},
{
"name": "HTTP trend",
"medianMs": 54.64,
"bytes": 13585,
"queries": 0
}
],
"baselineHttp": [
{
"name": "old HTTP positions",
"medianMs": 288.13,
"bytes": 6097025,
"queries": 0
},
{
"name": "old HTTP overview",
"medianMs": 992.35,
"bytes": 38920,
"queries": 0
}
],
"nestedSql": [
"SELECT `worthpath`.`Position`.`id`, `worthpath`.`Position`.`importedFromId`, `worthpath`.`Position`.`userId`, `worthpath`.`Position`.`kind`, `worthpath`.`Position`.`side`, `worthpath`.`Position`.`name`, `worthpath`.`Position`.`category`, `worthpath`.`Position`.`currency`, `worthpath`.`Position`.`notes`, `worthpath`.`Position`.`archived`, `worthpath`.`Position`.`hidden`, `worthpath`.`Position`.`createdAt`, `worthpath`.`Position`.`updatedAt`, `worthpath`.`Position`.`iconId` FROM `worthpath`.`Position` WHERE `worthpath`.`Position`.`userId` = ?",
"SELECT `worthpath`.`Revision`.`id`, `worthpath`.`Revision`.`sequence`, `worthpath`.`Revision`.`positionId`, `worthpath`.`Revision`.`amount`, `worthpath`.`Revision`.`effectiveDate`, `worthpath`.`Revision`.`notes`, `worthpath`.`Revision`.`reason`, `worthpath`.`Revision`.`createdAt`, `worthpath`.`Revision`.`updatedAt` FROM `worthpath`.`Revision` WHERE `worthpath`.`Revision`.`positionId` IN (?) ORDER BY `worthpath`.`Revision`.`effectiveDate` DESC, `worthpath`.`Revision`.`sequence` DESC LIMIT ? OFFSET ?"
],
"latestPlan": [
{
"f0": "-> Limit: 1 row(s) (cost=0.71 rows=1) (actual time=0.0158..0.0158 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '48c0ee00-069d-4443-9274-1d454c986c33') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=0.71 rows=1) (actual time=0.0153..0.0153 rows=1 loops=1)\n"
}
],
"pagePlan": [
{
"f0": "-> Append (cost=0..0 rows=51) (actual time=0.519..6.4 rows=51 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.519..0.519 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..70e-6 rows=1 loops=1)\n -> Select #2 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=2252 rows=1) (actual time=0.514..0.514 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 340720) (reverse), with index condition: ((x.positionId = '48c0ee00-069d-4443-9274-1d454c986c33') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 340720)))) (cost=2252 rows=5004) (actual time=0.513..0.513 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.148..0.148 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #4 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=2302 rows=1) (actual time=0.145..0.145 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 340719) (reverse), with index condition: ((x.positionId = '48c0ee00-069d-4443-9274-1d454c986c33') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 340719)))) (cost=2302 rows=5114) (actual time=0.145..0.145 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.141..0.141 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #6 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=2301 rows=1) (actual time=0.139..0.139 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 340718) (reverse), with index condition: ((x.positionId = '48c0ee00-069d-4443-9274-1d454c986c33') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 340718)))) (cost=2301 rows=5113) (actual time=0.139..0.139 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.146..0.146 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #8 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=2301 rows=1) (actual time=0.144..0.144 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 340717) (reverse), with index condition: ((x.positionId = '48c0ee00-069d-4443-9274-1d454c986c33') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 340717)))) (cost=2301 rows=5112) (actual time=0.144..0.144 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.136..0.136 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=60e-6..80e-6 rows=1 loops=1)\n -> Select #10 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=2300 rows=1) (actual time=0.134..0.134 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = '48c0ee00-069d-4443-9274-1d454c986c33' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 340716) (reverse), with index condition: ((x.positionId = '48c0ee00-069d-4443-9274-1d454c986c33') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 340716)))) (cost=2300 rows=5111) (actual time=0.134..0.134 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.134..0.134 rows=1 loops=1)Line truncated
}
],
"candidatePlans": [
[
{
"f0": "-> Limit: 51 row(s) (cost=501 rows=51) (actual time=0.0183..0.111 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='48c0ee00-069d-4443-9274-1d454c986c33') (reverse) (cost=501 rows=5003) (actual time=0.0179..0.108 rows=51 loops=1)\n"
}
]
]
},
{
"accounts": 1,
"revisionsPerAccount": 100000,
"baseline": [
{
"name": "old positions",
"medianMs": 2240.74,
"bytes": 61267028,
"queries": 5
},
{
"name": "old overview",
"medianMs": 10619.53,
"bytes": 39395,
"queries": 4
}
],
"optimized": [
{
"name": "new positions",
"medianMs": 2.74,
"bytes": 370,
"queries": 5
},
{
"name": "new current overview",
"medianMs": 2.13,
"bytes": 320,
"queries": 5
},
{
"name": "new history page",
"medianMs": 33.3,
"bytes": 16562,
"queries": 5
},
{
"name": "new trend",
"medianMs": 587.94,
"bytes": 13754,
"queries": 7
}
],
"httpResults": [
{
"name": "HTTP positions",
"medianMs": 5.56,
"bytes": 370,
"queries": 0
},
{
"name": "HTTP overview",
"medianMs": 5.39,
"bytes": 358,
"queries": 0
},
{
"name": "HTTP history",
"medianMs": 38.5,
"bytes": 16562,
"queries": 0
},
{
"name": "HTTP trend",
"medianMs": 585.14,
"bytes": 13854,
"queries": 0
}
],
"baselineHttp": [
{
"name": "old HTTP positions",
"medianMs": 2826.9,
"bytes": 61267028,
"queries": 0
},
{
"name": "old HTTP overview",
"medianMs": 9885.51,
"bytes": 39412,
"queries": 0
}
],
"nestedSql": [
"SELECT `worthpath`.`Position`.`id`, `worthpath`.`Position`.`importedFromId`, `worthpath`.`Position`.`userId`, `worthpath`.`Position`.`kind`, `worthpath`.`Position`.`side`, `worthpath`.`Position`.`name`, `worthpath`.`Position`.`category`, `worthpath`.`Position`.`currency`, `worthpath`.`Position`.`notes`, `worthpath`.`Position`.`archived`, `worthpath`.`Position`.`hidden`, `worthpath`.`Position`.`createdAt`, `worthpath`.`Position`.`updatedAt`, `worthpath`.`Position`.`iconId` FROM `worthpath`.`Position` WHERE `worthpath`.`Position`.`userId` = ?",
"SELECT `worthpath`.`Revision`.`id`, `worthpath`.`Revision`.`sequence`, `worthpath`.`Revision`.`positionId`, `worthpath`.`Revision`.`amount`, `worthpath`.`Revision`.`effectiveDate`, `worthpath`.`Revision`.`notes`, `worthpath`.`Revision`.`reason`, `worthpath`.`Revision`.`createdAt`, `worthpath`.`Revision`.`updatedAt` FROM `worthpath`.`Revision` WHERE `worthpath`.`Revision`.`positionId` IN (?) ORDER BY `worthpath`.`Revision`.`effectiveDate` DESC, `worthpath`.`Revision`.`sequence` DESC LIMIT ? OFFSET ?"
],
"latestPlan": [
{
"f0": "-> Limit: 1 row(s) (cost=0.71 rows=1) (actual time=0.0203..0.0203 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=0.71 rows=1) (actual time=0.0198..0.0198 rows=1 loops=1)\n"
}
],
"pagePlan": [
{
"f0": "-> Append (cost=0..0 rows=51) (actual time=0.634..35.5 rows=51 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.634..0.634 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..70e-6 rows=1 loops=1)\n -> Select #2 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=22711 rows=1) (actual time=0.626..0.626 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 440721) (reverse), with index condition: ((x.positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 440721)))) (cost=22711 rows=50467) (actual time=0.625..0.625 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.511..0.511 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #4 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=23215 rows=1) (actual time=0.506..0.506 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 440720) (reverse), with index condition: ((x.positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 440720)))) (cost=23215 rows=51588) (actual time=0.505..0.505 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.497..0.497 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #6 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=23215 rows=1) (actual time=0.493..0.493 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 440719) (reverse), with index condition: ((x.positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 440719)))) (cost=23215 rows=51587) (actual time=0.493..0.493 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.436..0.436 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=50e-6..70e-6 rows=1 loops=1)\n -> Select #8 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=23214 rows=1) (actual time=0.432..0.432 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 440718) (reverse), with index condition: ((x.positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 440718)))) (cost=23214 rows=51586) (actual time=0.432..0.432 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.424..0.424 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #10 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=23214 rows=1) (actual time=0.421..0.421 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate < '2026-09-30 16:00:00.000') OR (positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de' AND effectiveDate = '2026-09-30 16:00:00.000' AND sequence < 440717) (reverse), with index condition: ((x.positionId = 'd4a20795-cbba-4260-8958-1a19c3fd49de') and ((x.effectiveDate < TIMESTAMP'2026-09-30 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-09-30 16:00:00') and (x.sequence < 440717)))) (cost=23214 rows=51585) (actual time=0.421..0.421 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.492..0.492Line truncated
}
],
"candidatePlans": [
[
{
"f0": "-> Limit: 51 row(s) (cost=6160 rows=51) (actual time=0.0321..0.211 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='d4a20795-cbba-4260-8958-1a19c3fd49de') (reverse) (cost=6160 rows=50466) (actual time=0.0313..0.208 rows=51 loops=1)\n"
}
]
]
},
{
"accounts": 10,
"revisionsPerAccount": 10000,
"baseline": [
{
"name": "old positions",
"medianMs": 2091.34,
"bytes": 60970241,
"queries": 5
},
{
"name": "old overview",
"medianMs": 16928.24,
"bytes": 199599,
"queries": 5
}
],
"optimized": [
{
"name": "new positions",
"medianMs": 4.17,
"bytes": 3671,
"queries": 5
},
{
"name": "new current overview",
"medianMs": 2.98,
"bytes": 2100,
"queries": 5
},
{
"name": "new history page",
"medianMs": 39.58,
"bytes": 16471,
"queries": 14
},
{
"name": "new trend",
"medianMs": 582.64,
"bytes": 13755,
"queries": 7
}
],
"httpResults": [
{
"name": "HTTP positions",
"medianMs": 6.19,
"bytes": 3671,
"queries": 0
},
{
"name": "HTTP overview",
"medianMs": 5.48,
"bytes": 2138,
"queries": 0
},
{
"name": "HTTP history",
"medianMs": 34.32,
"bytes": 16471,
"queries": 0
},
{
"name": "HTTP trend",
"medianMs": 568.12,
"bytes": 13855,
"queries": 0
}
],
"baselineHttp": [
{
"name": "old HTTP positions",
"medianMs": 2869.42,
"bytes": 60970241,
"queries": 0
},
{
"name": "old HTTP overview",
"medianMs": 15636.96,
"bytes": 199616,
"queries": 0
}
],
"nestedSql": [
"SELECT `worthpath`.`Position`.`id`, `worthpath`.`Position`.`importedFromId`, `worthpath`.`Position`.`userId`, `worthpath`.`Position`.`kind`, `worthpath`.`Position`.`side`, `worthpath`.`Position`.`name`, `worthpath`.`Position`.`category`, `worthpath`.`Position`.`currency`, `worthpath`.`Position`.`notes`, `worthpath`.`Position`.`archived`, `worthpath`.`Position`.`hidden`, `worthpath`.`Position`.`createdAt`, `worthpath`.`Position`.`updatedAt`, `worthpath`.`Position`.`iconId` FROM `worthpath`.`Position` WHERE `worthpath`.`Position`.`userId` = ?",
"SELECT `worthpath`.`Revision`.`id`, `worthpath`.`Revision`.`sequence`, `worthpath`.`Revision`.`positionId`, `worthpath`.`Revision`.`amount`, `worthpath`.`Revision`.`effectiveDate`, `worthpath`.`Revision`.`notes`, `worthpath`.`Revision`.`reason`, `worthpath`.`Revision`.`createdAt`, `worthpath`.`Revision`.`updatedAt` FROM `worthpath`.`Revision` WHERE `worthpath`.`Revision`.`positionId` IN (?,?,?,?,?,?,?,?,?,?) ORDER BY `worthpath`.`Revision`.`effectiveDate` DESC, `worthpath`.`Revision`.`sequence` DESC"
],
"latestPlan": [
{
"f0": "-> Append (cost=87744 rows=10) (actual time=0.0191..0.113 rows=10 loops=1)\n -> Stream results (cost=8960 rows=1) (actual time=0.0187..0.0188 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8960 rows=1) (actual time=0.016..0.0161 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'abe0b945-e334-4020-8d28-1d4e03266739' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = 'abe0b945-e334-4020-8d28-1d4e03266739') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8960 rows=19910) (actual time=0.0155..0.0155 rows=1 loops=1)\n -> Stream results (cost=8960 rows=1) (actual time=0.0115..0.0116 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8960 rows=1) (actual time=0.0102..0.0102 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '7a7b7ab6-2d18-4ddc-87d5-b7d673d03616' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '7a7b7ab6-2d18-4ddc-87d5-b7d673d03616') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8960 rows=19910) (actual time=0.01..0.01 rows=1 loops=1)\n -> Stream results (cost=8225 rows=1) (actual time=0.00945..0.00951 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8225 rows=1) (actual time=0.00863..0.00865 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '928ec1d9-dfe2-4636-af8c-9ef882778826' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '928ec1d9-dfe2-4636-af8c-9ef882778826') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8225 rows=18278) (actual time=0.00854..0.00854 rows=1 loops=1)\n -> Stream results (cost=8960 rows=1) (actual time=0.00986..0.00992 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8960 rows=1) (actual time=0.00918..0.0092 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '26113ccf-1d6a-4f2e-bef4-d2b2845fa44e' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '26113ccf-1d6a-4f2e-bef4-d2b2845fa44e') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8960 rows=19910) (actual time=0.00893..0.00893 rows=1 loops=1)\n -> Stream results (cost=8225 rows=1) (actual time=0.0103..0.0103 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8225 rows=1) (actual time=0.00963..0.00965 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '2965aade-bb10-45bc-a172-f6729f591856' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '2965aade-bb10-45bc-a172-f6729f591856') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8225 rows=18278) (actual time=0.00951..0.00951 rows=1 loops=1)\n -> Stream results (cost=8732 rows=1) (actual time=0.0104..0.0105 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8732 rows=1) (actual time=0.00979..0.00981 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'f757987e-153c-44aa-91db-8abe018468f1' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = 'f757987e-153c-44aa-91db-8abe018468f1') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8732 rows=19404) (actual time=0.00964..0.00964 rows=1 loops=1)\n -> Stream results (cost=8225 rows=1) (actual time=0.00932..0.00939 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8225 rows=1) (actual time=0.00839..0.00841 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '2a00d18e-5529-4160-afde-ef02be8af760' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '2a00d18e-5529-4160-afde-ef02be8af760') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=8225 rows=18278) (actual time=0.00831..0.00831 rows=1 loops=1)\n -> Stream results (cost=9349 rows=1) (actual time=0.00764..0.0077 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=9349 rows=1) (actual time=0.00697..0.00699 rows=1 loops=1)\n -> Index range scan on r using Revision_positionId_effectiveDate_sequence_idx over (positionId = '74118fd8-00d5-47fe-974c-db6bf2b966eb' AND effectiveDate < '2026-10-02 16:00:00.000') (reverse), with index condition: ((r.positionId = '74118fd8-00d5-47fe-974c-db6bf2b966eb') and (r.effectiveDate < TIMESTAMP'2026-10-02 16:00:00')) (cost=9349 rows=20774) (actual time=0.00672..0.00672 rows=1 loops=1)\n -> Stream results (cost=8377 rows=1) (actual time=0.00917..0.00924 rows=1 loops=1)\n -> Limit: 1 row(s) (cost=8377 rows=1) (actual time=0.008..0.00802 rowsLine truncated
}
],
"pagePlan": [
{
"f0": "-> Append (cost=0..0 rows=51) (actual time=0.729..13 rows=51 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.729..0.729 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..170e-6 rows=1 loops=1)\n -> Select #2 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=8955 rows=1) (actual time=0.721..0.721 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'abe0b945-e334-4020-8d28-1d4e03266739' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = 'abe0b945-e334-4020-8d28-1d4e03266739' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 540859) (reverse), with index condition: ((x.positionId = 'abe0b945-e334-4020-8d28-1d4e03266739') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 540859)))) (cost=8955 rows=19899) (actual time=0.72..0.72 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.746..0.746 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=50e-6..70e-6 rows=1 loops=1)\n -> Select #4 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=8955 rows=1) (actual time=0.74..0.74 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '7a7b7ab6-2d18-4ddc-87d5-b7d673d03616' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = '7a7b7ab6-2d18-4ddc-87d5-b7d673d03616' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 530859) (reverse), with index condition: ((x.positionId = '7a7b7ab6-2d18-4ddc-87d5-b7d673d03616') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 530859)))) (cost=8955 rows=19899) (actual time=0.74..0.74 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.747..0.747 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..70e-6 rows=1 loops=1)\n -> Select #6 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=8221 rows=1) (actual time=0.741..0.741 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '928ec1d9-dfe2-4636-af8c-9ef882778826' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = '928ec1d9-dfe2-4636-af8c-9ef882778826' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 520859) (reverse), with index condition: ((x.positionId = '928ec1d9-dfe2-4636-af8c-9ef882778826') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 520859)))) (cost=8221 rows=18267) (actual time=0.741..0.741 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.62..0.62 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #8 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=8955 rows=1) (actual time=0.613..0.613 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '26113ccf-1d6a-4f2e-bef4-d2b2845fa44e' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = '26113ccf-1d6a-4f2e-bef4-d2b2845fa44e' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 510859) (reverse), with index condition: ((x.positionId = '26113ccf-1d6a-4f2e-bef4-d2b2845fa44e') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 510859)))) (cost=8955 rows=19899) (actual time=0.613..0.613 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.626..0.626 rows=1 loops=1)\n -> Rows fetched before execution (cost=0..0 rows=1) (actual time=40e-6..60e-6 rows=1 loops=1)\n -> Select #10 (subquery in projection; run only once)\n -> Limit: 1 row(s) (cost=8221 rows=1) (actual time=0.621..0.621 rows=1 loops=1)\n -> Index range scan on x using Revision_positionId_effectiveDate_sequence_idx over (positionId = '2965aade-bb10-45bc-a172-f6729f591856' AND effectiveDate < '2026-10-01 16:00:00.000') OR (positionId = '2965aade-bb10-45bc-a172-f6729f591856' AND effectiveDate = '2026-10-01 16:00:00.000' AND sequence < 500859) (reverse), with index condition: ((x.positionId = '2965aade-bb10-45bc-a172-f6729f591856') and ((x.effectiveDate < TIMESTAMP'2026-10-01 16:00:00') or ((x.effectiveDate = TIMESTAMP'2026-10-01 16:00:00') and (x.sequence < 500859)))) (cost=8221 rows=18267) (actual time=0.621..0.621 rows=1 loops=1)\n -> Stream results (cost=0..0 rows=1) (actual time=0.644..0.644 rows=1 loops=1)\n Line truncated
}
],
"candidatePlans": [
[
{
"f0": "-> Limit: 51 row(s) (cost=3100 rows=51) (actual time=0.0512..0.164 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='26113ccf-1d6a-4f2e-bef4-d2b2845fa44e') (reverse) (cost=3100 rows=19920) (actual time=0.0509..0.162 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=2937 rows=51) (actual time=0.00983..0.104 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='2a00d18e-5529-4160-afde-ef02be8af760') (reverse) (cost=2937 rows=18288) (actual time=0.00967..0.102 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=3100 rows=51) (actual time=0.0115..0.111 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='7a7b7ab6-2d18-4ddc-87d5-b7d673d03616') (reverse) (cost=3100 rows=19920) (actual time=0.0113..0.108 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=2937 rows=51) (actual time=0.0128..0.133 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='928ec1d9-dfe2-4636-af8c-9ef882778826') (reverse) (cost=2937 rows=18288) (actual time=0.0126..0.13 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=3299 rows=51) (actual time=0.0112..0.123 rows=51 loops=1)\n -> Index range scan on Revision using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'f757987e-153c-44aa-91db-8abe018468f1') (reverse), with index condition: (revision.positionId = 'f757987e-153c-44aa-91db-8abe018468f1') (cost=3299 rows=19414) (actual time=0.011..0.121 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=3324 rows=51) (actual time=0.0205..0.141 rows=51 loops=1)\n -> Index range scan on Revision using Revision_positionId_effectiveDate_sequence_idx over (positionId = 'abe0b945-e334-4020-8d28-1d4e03266739') (reverse), with index condition: (revision.positionId = 'abe0b945-e334-4020-8d28-1d4e03266739') (cost=3324 rows=19920) (actual time=0.0203..0.138 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=2971 rows=51) (actual time=0.0266..0.191 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='696e54a9-ee0b-45f0-b634-b08c26e4a902') (reverse) (cost=2971 rows=18624) (actual time=0.0261..0.188 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=3418 rows=51) (actual time=0.0229..0.199 rows=51 loops=1)\n -> Index range scan on Revision using Revision_positionId_effectiveDate_sequence_idx over (positionId = '1abba907-af9b-447b-b281-84fc7b08b6d9') (reverse), with index condition: (revision.positionId = '1abba907-af9b-447b-b281-84fc7b08b6d9') (cost=3418 rows=21636) (actual time=0.022..0.196 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=2937 rows=51) (actual time=0.0102..0.0974 rows=51 loops=1)\n -> Index lookup on Revision using Revision_positionId_effectiveDate_sequence_idx (positionId='2965aade-bb10-45bc-a172-f6729f591856') (reverse) (cost=2937 rows=18288) (actual time=0.0101..0.0951 rows=51 loops=1)\n"
}
],
[
{
"f0": "-> Limit: 51 row(s) (cost=3360 rows=51) (actual time=0.0184..0.249 rows=51 loops=1)\n -> Index range scan on Revision using Revision_positionId_effectiveDate_sequence_idx over (positionId = '74118fd8-00d5-47fe-974c-db6bf2b966eb') (reverse), with index condition: (revision.positionId = '74118fd8-00d5-47fe-974c-db6bf2b966eb') (cost=3360 rows=20786) (actual time=0.018..0.246 rows=51 loops=1)\n"
}
]
]
}
]
}
+73
View File
@@ -0,0 +1,73 @@
# 查询优化、关联记账与多语言验收(2026-10-02)
实际环境:Node 24.21.0、MySQL 8.0.45、Prisma 6.19.0、NestJS 11.2.6、React 19、Vite 7.3.6、TypeScript 5.9.3,以锁文件为准。保留 MySQL、Decimal 和已有用户隔离规则;归档项目仍参与总额。Revision.amount 是变更后的余额。
## 查询与计算
- `/positions?kind=account|asset|debt` 只带当前余额、必要资料、可见关联及本位币折算;`/positions/:id` 返回必要资料与当前原币余额。两者不带历史。
- `/overview` 只带当前总资产、负债、净资产、必要明细及汇率状态。
- `/history`、`/positions/:id/history`、`/transfers` 支持 `limit`、`cursor`、`from`、`to`;历史另支持 `positionId`。默认 50,最多 100,最近流水使用同一接口 limit=20。返回 `{items,nextCursor,revealed}`。历史按 effectiveDate DESC、唯一 sequence DESC;往来按 effectiveDate DESC、唯一 id DESC。
- 历史先从每个可见项目的复合索引取至多 limit+1 条,再合并页;只对入选记录定位真实前序余额,分页首条的 before 不假定为零。项目非常多时,查询数仍随项目数增长。
- 最新余额使用按项目分支的参数化 UNION ALL,每个分支按 `[positionId,effectiveDate,sequence]` 反向索引扫描 LIMIT 1;最多 100 个分支一批。没有反规范化当前余额字段,因此无需余额回填或缓存重建,历史补录、更正、导入恢复后直接从历史重算。
- 已捕获 Prisma 实际 SQL:单父项目 nested take:1 有数据库 LIMIT;多父项目 nested take:1 产生无 LIMIT 的 revisions 查询,由客户端裁剪。不能仅靠 take 判断查询代价。本次使用明确的数据库分支限制。
- 更正只查目标及相关配对约束。不能改配对记录或在其之前插入/更正;同业务时间使用 sequence 比较,配对后同分钟的普通余额记录可以更正。保留业务约束,避免破坏双方账。
- `/trend` 默认最近 90 天。day 最多 366 天、week 最多 1096 天、month 最多 3653 天。使用区间前最后余额和汇率作种子,数据库取范围内每日最后余额;应用排序一次并顺序推进。响应不带每日期完整项目明细。
- 周以周一至周日、月以自然月取期末余额;变化归因累加该段每日余额变化与汇率变化,首尾不完整周期按实际范围。缺失汇率标记不完整,沿用时保留实际 rateDate。仅查询相关币种、本位币的区间汇率和必要前序汇率;当前总额只查询最新可用汇率。
## 实测证据
随机独立测试用户,原服务 3100 与优化后隔离服务 33101 使用同一数据库;每项 3 次取中位数,全部为实际 HTTP 测量,响应大小为未压缩 JSON 字节。测试记录集中在 90 天内,金额虚构,测试结束清理该测试用户。完整 SQL、EXPLAIN ANALYZE、处理函数计时及 HTTP 结果见 [原始证据](performance-final.json)。原始证据中的 HTTP queries=0 表示远端 HTTP 客户端未采集 SQL 事件,不代表数据库零查询。
| 数据 | 账户列表:前 → 后 | 列表响应:前 → 后 | 当前总览:前 → 后 | 历史 50 条 | 日趋势 90 天 |
| -------------- | -----------------: | -------------------: | ------------------: | ---------: | -----------: |
| 单账户 1 万 | 288.13 → 11.59 ms | 6,097,025 → 368 B | 992.35 → 7.36 ms | 19.20 ms | 54.64 ms |
| 单账户 10 万 | 2,826.90 → 5.56 ms | 61,267,028 → 370 B | 9,885.51 → 5.39 ms | 38.50 ms | 585.14 ms |
| 10 账户各 1 万 | 2,869.42 → 6.19 ms | 60,970,241 → 3,671 B | 15,636.96 → 5.48 ms | 34.32 ms | 568.12 ms |
单账户 10 万条 EXPLAIN ANALYZE:最新余额索引节点实际 rows=1 loops=1;历史候选索引节点 rows=51 loops=1;每条前序余额子查询实际 rows=1。多账户每分支分别限量。资金往来最终采用 FORCE INDEX + STRAIGHT_JOIN,以用户/时间/id 为驱动;60 条同时间测试记录,首批索引实际 rows=51,游标下一页 rows=10,无全量排序,见 [往来分页计划](transfers-plan.json)。隐藏或指定账户过滤需要跳过不匹配行,实际扫描可多于返回条数。旧列表实际返回全部 1 万/10 万历史,旧总览也读取全部历史;没有采集旧查询精确存储引擎 rows_examined,因此不将返回行数冒充扫描统计。新增全局日期索引曾导致优化器选择全局历史路径,已通过第二迁移移除,最终使用项目复合索引。EXPLAIN 中 cost/estimated rows 与 actual rows 含义不同。
前端请求日志及浏览器实测:总览为 overview、trend、history(limit20) 共 3 个财务 GET;账户列表 1 个;详情 2 个;加载下一页只 1 个 history;变化记录页 2 个;打开资金往来表单才取账户/债务选项。旧统一加载为 positions、overview、transfers、settings 共 4 个 GET,各页面都请求全部历史。总览现在虽为 3 个 GET,但每个职责有界;账户列表从 4 降到 1。语言切换为 0 个财务 GET。认证 me 和交互活动另计;启动 me 通过共享 Promise 避免开发 StrictMode 重复。日期范围改变只请求 trend;过期响应由会话、加载序号和当前选择校验拦截。
这些是本机对当前数据库的结果,不是生产并发/p95 保证。区间内 10 万条趋势仍需数据库窗口排序,约 0.6 秒;未引入每日快照、缓存或新基础设施。游标页在并发历史更正后应重新加载,未提供跨请求冻结快照。
## 借入、借出及手续费
债务 side=liability 表示借入/应付,side=asset 表示借出/应收。资金往来复用 `/transfers`,operation 为 transfer、borrow、lend、collect、repay。来源为启用中的资产账户,目标为另一资产账户或对应方向的债务。同币种账户本金与债务本金相等,跨币种填写实际金额。
借入到账:账户 +(amount-fee),应付 +received;借出:账户 -(amount+fee),应收 +received;收回应收:账户 +(amount-fee),应收 -received;偿还应付:账户 -(amount+fee),应付 -received。手续费可为负数表示优惠,绝对优惠不超过本金;收款正手续费不超过本金;禁止负余额或超额还款。金额仍为 Decimal。
双方行按 ID 顺序加锁,最新余额读取、幂等校验、双边历史、往来记录及自动关联同处 Serializable 事务。死锁/写冲突有限重试(最多 3 次重试)。账户详情预选当前账户;债务详情按关联预选账户。借入/借出操作、流水、详情互相可导航。新建债务可先录已有余额;新发生借贷建议建 0 余额后使用联动操作,避免重复计入。
ZIP v6 增加 operation;保留 v3/v4/v5 ZIP、v1/v2 JSON 读取。恢复校验双边前后余额、操作方向、费用和配对原因,不改变历史金额含义。
## 三种语言
顶部及登录页支持简中、English、繁中,语言保存在本浏览器 localStorage。静态词典覆盖页面、表单、图标交互和常见服务端错误;用户名称、备注、原币金额保持原文。繁中由开发时 OpenCC 生成静态 JSON,不在浏览器运行转换。修改英文词典后执行 `node apps/web/scripts/generate-locales.cjs`;安全清空确认短语仍精确要求“确定清空”。
## 迁移、部署与复验
本次已对配置数据库执行 migrate deploy,四个迁移:
1. `20261002000000_query_indexes`:Revision 项目/日期/顺序及配对约束索引、Transfer 用户/日期/id 索引。
2. `20261002000100_remove_global_revision_index`:移除实测不合适的全局日期索引。
3. `20261002000200_debt_movements`:Transfer.operation 默认 transfer,旧记录自动获得原转账语义。
4. `20261002000300_chinese_comments`:8 张业务表及 73 个字段增加中文注释,Prisma 模型同步文档注释。迁移保留 SHOW CREATE TABLE 原列定义,不改变字段类型、默认值和自增;数据库元数据验证见 [注释核验](database-comments.json)。Prisma 自身的迁移元数据表不作为业务表修改。
无余额列回填;未删除真实历史。其他环境需先备份,停止 API 释放 Windows Prisma DLL,再按下面步骤部署同步后端与前端:
```powershell
pnpm install --frozen-lockfile
pnpm db:generate
pnpm db:migrate
pnpm db:status
pnpm typecheck
pnpm build
pnpm test
# 启动优化后的 API 后
$env:TEST_API_URL='http://127.0.0.1:3100/api'
pnpm test:integration
```
最终验证:类型检查和生产构建通过;18 个单元测试、8 个真实 MySQL 集成测试通过。涵盖历史补录/更正、同时间顺序、前序余额/分页、归档、隐藏解锁/用户隔离、缺失汇率/本位币切换、负手续费、并发幂等转账与收款、借入借出/还款、ZIP 完整恢复。浏览器验证了 50+10 条分页、详情转账预选、债务关联导航、还款表单默认账户及中英繁切换/刷新持久化。
基准可重跑:在 apps/api 执行 `pnpm exec tsx scripts/performance.ts rerun`;TEST_API_URL 指向优化后服务,BASELINE_API_URL 可选指向独立旧版本服务,结果输出 docs/performance-rerun.json。脚本只生成/清理随机测试用户。连续反复认证集成测试可能触发现有每 IP 15 分钟 30 次认证限速;本次通过重启隔离测试进程复验,生产限制未放宽。
+16
View File
@@ -0,0 +1,16 @@
{
"sql": "\n SELECT t.id FROM Transfer t FORCE INDEX (Transfer_userId_effectiveDate_id_idx)\n STRAIGHT_JOIN Position s ON s.id = t.sourceId\n STRAIGHT_JOIN Position d ON d.id = t.targetId\n WHERE t.userId = ? AND s.userId = ? AND d.userId = ?\n AND s.hidden = false AND d.hidden = false\n \n \n \n \n ORDER BY t.effectiveDate DESC, t.id DESC LIMIT ?",
"plan": [
{
"f0": "-> Limit: 51 row(s) (cost=30.6 rows=0.6) (actual time=0.022..0.122 rows=51 loops=1)\n -> Nested loop inner join (cost=30.6 rows=0.6) (actual time=0.0216..0.12 rows=51 loops=1)\n -> Nested loop inner join (cost=28.5 rows=6) (actual time=0.0197..0.0934 rows=51 loops=1)\n -> Index lookup on t using Transfer_userId_effectiveDate_id_idx (userId='75778c01-f278-42e1-bc07-85331d1be657') (reverse) (cost=7.5 rows=60) (actual time=0.0155..0.0644 rows=51 loops=1)\n -> Filter: ((s.hidden = false) and (s.userId = '75778c01-f278-42e1-bc07-85331d1be657')) (cost=0.25 rows=0.1) (actual time=391e-6..452e-6 rows=1 loops=51)\n -> Single-row index lookup on s using PRIMARY (id=t.sourceId) (cost=0.25 rows=1) (actual time=124e-6..144e-6 rows=1 loops=51)\n -> Filter: ((d.hidden = false) and (d.userId = '75778c01-f278-42e1-bc07-85331d1be657')) (cost=0.252 rows=0.1) (actual time=345e-6..406e-6 rows=1 loops=51)\n -> Single-row index lookup on d using PRIMARY (id=t.targetId) (cost=0.252 rows=1) (actual time=93.5e-6..113e-6 rows=1 loops=51)\n"
}
],
"cursorSql": "\n SELECT t.id FROM Transfer t FORCE INDEX (Transfer_userId_effectiveDate_id_idx)\n STRAIGHT_JOIN Position s ON s.id = t.sourceId\n STRAIGHT_JOIN Position d ON d.id = t.targetId\n WHERE t.userId = ? AND s.userId = ? AND d.userId = ?\n AND s.hidden = false AND d.hidden = false\n \n \n \n AND (t.effectiveDate < ? OR\n (t.effectiveDate = ? AND t.id < ?))\n ORDER BY t.effectiveDate DESC, t.id DESC LIMIT ?",
"cursorPlan": [
{
"f0": "-> Limit: 51 row(s) (cost=9.7 rows=0.11) (actual time=0.0221..0.046 rows=10 loops=1)\n -> Nested loop inner join (cost=9.7 rows=0.11) (actual time=0.0218..0.0453 rows=10 loops=1)\n -> Nested loop inner join (cost=9.31 rows=1.1) (actual time=0.0199..0.0387 rows=10 loops=1)\n -> Index range scan on t using Transfer_userId_effectiveDate_id_idx over (userId = '75778c01-f278-42e1-bc07-85331d1be657' AND effectiveDate < '2026-10-01 04:00:00.000') OR (userId = '75778c01-f278-42e1-bc07-85331d1be657' AND effectiveDate = '2026-10-01 04:00:00.000' AND id < '475b7d99-f250-44cb-a7ea-d75c9c3d39fa') (reverse), with index condition: ((t.userId = '75778c01-f278-42e1-bc07-85331d1be657') and ((t.effectiveDate < TIMESTAMP'2026-10-01 04:00:00') or ((t.effectiveDate = TIMESTAMP'2026-10-01 04:00:00') and (t.id < '475b7d99-f250-44cb-a7ea-d75c9c3d39fa')))) (cost=5.46 rows=11) (actual time=0.0157..0.0295 rows=10 loops=1)\n -> Filter: ((s.hidden = false) and (s.userId = '75778c01-f278-42e1-bc07-85331d1be657')) (cost=0.251 rows=0.1) (actual time=664e-6..728e-6 rows=1 loops=10)\n -> Single-row index lookup on s using PRIMARY (id=t.sourceId) (cost=0.251 rows=1) (actual time=366e-6..386e-6 rows=1 loops=10)\n -> Filter: ((d.hidden = false) and (d.userId = '75778c01-f278-42e1-bc07-85331d1be657')) (cost=0.259 rows=0.1) (actual time=470e-6..534e-6 rows=1 loops=10)\n -> Single-row index lookup on d using PRIMARY (id=t.targetId) (cost=0.259 rows=1) (actual time=208e-6..230e-6 rows=1 loops=10)\n"
}
],
"candidateRows": 51,
"nextPageRows": 10
}