chore(task): archive 09-07-api-performance-diagnosis
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "后端必要检查与测试形状"}
|
||||
{"file": ".trellis/spec/backend/http-api-contracts.md", "reason": "HTTP 兼容性"}
|
||||
{"file": ".trellis/tasks/09-07-api-performance-diagnosis/design.md", "reason": "已批准设计与性能证据"}
|
||||
@@ -0,0 +1,44 @@
|
||||
# 性能诊断与建议设计(已批准实施)
|
||||
|
||||
## 证据与边界
|
||||
2026-09-07,低频公网 GET:示例 detail 四次 3.045 / 3.059 / 3.074 / 3.442 秒,200,10750 字节;显式绕过本机代理复测 3.312557 秒,TLS 完成 0.443439 秒,首字节 3.312484 秒。直连 healthz 0.810132 秒、dates 0.815043 秒、ratio 榜单 2.570251 秒。默认网络路径 history 1.834179 秒、2020-01-01 无数据 detail 0.746555 秒。请求成功验证示例为 SPD概念,成员 14 个。
|
||||
|
||||
这些是客户端端到端耗时,不是服务端或 SQL 独立耗时;样本不足以推断 P95、并发容量或全站所有接口。生产代码版本、CPU/IO/锁等待及 SQL 执行计划未验证。
|
||||
|
||||
## 当前调用链
|
||||
- presentation/http.py:355 -> ReadRadarDetails.detail -> history -> history_data。
|
||||
- application/details.py:145 的 history_data 加载最多 30 个发布日的全部排名;为了取得 calendar,调用 snapshots 加载整个批次全部原始输入。
|
||||
- application/details.py:294 的 detail 再次调用 snapshots,并在 Python 中重建股票基础信息、全体成员关系、行情和资金索引,再计算相似板块。
|
||||
- infrastructure/postgres.py:194 的 load_publication_sources 按 publication_id 连接来源表和快照表,SELECT 包括完整 JSONB payload,没有 source_group 或证券过滤;:1112 还把每个 payload 行复制为 dict。
|
||||
- application/details.py:182 的 ranking_extras 具有相同的重复读取模式;read.py:285 在 amount/ratio 榜单调用它。
|
||||
- presentation/http.py:258 已按进程缓存仓储;postgres.py:48 默认连接池上限 4。当前部署 Dockerfile:48 没有显式指定 worker 数量,但生产环境覆盖与并发压力未知,不能据此确诊排队。
|
||||
|
||||
本地使用 tests/unit/sector_radar/test_read.py 的内存仓储夹具,仅代理计数真实应用层调用,不改产品代码:detail 为 get_successful_publication=1, load_history_publications=1, load_publication_rankings=1, load_publication_sources=2;普通金额榜单另有 load_rankings=1。此实验确认调用次数,不测量生产 SQL 成本。
|
||||
|
||||
## 已批准的优化次序
|
||||
1. 取得服务端分段计时与只读查询计划,分别测连接池等待、SQL 执行与取数、JSON 转换、Python 组装;先对最重的快照读取确认行数和字节数。
|
||||
2. 日历只读取 calendar 来源;同一请求避免重复读取同一发布输入,先减少明显多余工作。
|
||||
3. 详情优先读取既有 publication 归属的事实和聚合投影;成员及股票只取所需范围,相似板块考虑在发布阶段预计算。旧批次投影缺失必须保持当前缺失语义,不能换用全局最新成员或直接读 Tushare。
|
||||
4. 历史请求保留同类同版本的真实排名池大小、名次、百分位和缺失状态,SQL 只返回所需板块结果与分组统计,避免每次构造全量排名对象并重复扫描。
|
||||
5. 仅在查询计划显示需要时提出索引;当前 publication/source/ranking 已有主键及索引,不能笼统归因为缺索引。
|
||||
6. 优化冷请求后,依据重复访问与并发数据决定是否加入有界进程缓存或 Redis。
|
||||
|
||||
## Redis 取舍与一致性
|
||||
Redis 可缓存最终响应/紧凑投影,适合读多写少的已发布收盘数据,尤其多进程/多实例需要共享结果时。它不是当前诊断的必要前提;只安装服务并不加速,必须接入读取、写入和失效逻辑,未命中仍走原查询。
|
||||
|
||||
缓存键至少含响应 schema 版本、请求参数、当前 publication_id;含历史曲线的响应还依赖此前各日选中的 publication_id/source_version/metric_version,历史补录或重建也必须改变键或触发失效,不能仅使用目标日期或当前批次 ID。缓存只保存成功且版本明确的投影,设置容量上限、TTL、并发回填保护和故障回源。TTL 不替代明确的发布版本语义。
|
||||
|
||||
官方资料:https://redis.io/docs/latest/develop/use-cases/cache-aside/ ,已经 Context7 与官方网页核验通用 cache-aside、TTL 和显式失效机制;当前项目未发现 Redis 依赖,未选择版本。
|
||||
|
||||
## 兼容与回滚
|
||||
不改变 HTTP 字段、精度、历史缺失语义、最后有效发布规则。缓存层应可关闭回源;如后续需要新增投影或迁移,应先独立评审与授权。用户已批准本地实现与验证;线上发布由用户负责。
|
||||
|
||||
## 本轮落实的读取设计
|
||||
- 新增 publication-scoped 原始行投影读取接口,明确 sources、trade_date、ts_codes 过滤,不伪造带原快照哈希的裁剪快照。保留 source_order 和快照内行顺序,保证重复键覆盖行为不变。
|
||||
- 历史只读 calendar;榜单额外只读指数;详情先读取指数/股票基础/成员用于当前上市池和重合率,再按目标日与成员读取 daily/moneyflow_dc/moneyflow。
|
||||
- 新增按所需板块读取历史排名的查询,完整排名池分组统计在过滤目标板块前完成;不存在板块也保留该池大小。应用层一次建立按板块、指标、版本的查找表,避免循环扫描。
|
||||
- 既有 stock_fact 非 available 会清除 net_amount 且没有 publication_id,旧字段也可能空,不能直接无损替代独立来源读数;本轮不迁移、不重建、不引入全局缓存。相似度在请求内基于必要成员集计算。
|
||||
- 性能目标用同一隔离 PostgreSQL 数据集前后对比与传输范围断言验证,实际公网改善由用户发布后验证。
|
||||
|
||||
## 验证结论
|
||||
实现只改四个 sector_radar 后端文件,新增读取回归测试文件。无迁移、依赖、HTTP 字段或全局缓存改动。真实数据库结果、完整响应一致性及前后耗时记录在 research/performance.json。详情仍需要相似板块和上市过滤使用的完整成员/基础信息;不兼容来源版本的少量池统计仍在查询后丢弃,这两点保留为后续测量候选。
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "后端必要检查与测试形状"}
|
||||
{"file": ".trellis/spec/backend/http-api-contracts.md", "reason": "HTTP 兼容性"}
|
||||
{"file": ".trellis/tasks/09-07-api-performance-diagnosis/design.md", "reason": "已批准设计与性能证据"}
|
||||
@@ -0,0 +1,30 @@
|
||||
# 后续执行建议(已批准实施)
|
||||
|
||||
## 当前已完成
|
||||
- [x] 公网低频请求复现与分段计时,包含显式直连对照。
|
||||
- [x] 检查路由、应用层、仓储、既有迁移和部署配置。
|
||||
- [x] 用现有内存仓储夹具追踪重复读取,未修改产品代码。
|
||||
- [x] Redis 取舍与发布/历史依赖失效边界分析。
|
||||
|
||||
## 实施顺序
|
||||
1. 核对生产版本并收集脱敏的 server/request 分段耗时、池等待、查询行数与字节数;针对 SELECT 使用只读执行计划并设超时,不做生产压力测试。
|
||||
2. 审阅需修改文件全文并加载 backend 规格,明确最终性能目标;补齐真实 implement/check 上下文清单后再 task.py start。
|
||||
3. 优先修复完整快照重复读取和日历过量读取,增加能约束调用次数、过滤范围及历史一致性的回归测试。
|
||||
4. 根据实测再决定缩小排名结果、使用既有投影或新增预计算。每项范围变化都更新设计;不以增加连接数或 worker 数替代测量。
|
||||
5. 运行受影响测试、后端规定的 Ruff、Pyright 和 pytest;固定样本比较输出语义与耗时。在获批的环境验证冷/热请求与必要并发,不凭公网少量样本宣称 P95。
|
||||
6. 仅在确认缓存需求后评审 Redis;验证重建、历史补录、并发回填、容量淘汰和 Redis 不可用时回源。
|
||||
|
||||
## 风险与授权
|
||||
用户已明确批准按方案实施本地优化和验证。隔离本地 PostgreSQL 用于查询正确性与对比,禁止连接生产执行写入或压测。用户随后明确要求提交到本地 develop;不推送、不部署、不更新共享规范。
|
||||
|
||||
## 本轮实现与验证结果
|
||||
- [x] publication 行投影查询按 source_group、目标日、ts_code 过滤,source_order/ordinality 稳定,无原始快照重复取数。
|
||||
- [x] 历史排名在 SQL 中统计完整池后过滤板块;应用层索引查找;缺失板块、旧指标版本、不兼容来源版本、请求期间重建钉住均覆盖。
|
||||
- [x] 真实 PostgreSQL 隔离 schema 验证,旧版仅原始快照数据仍可读取独立缺失值;未排行和行业池不会污染概念池。
|
||||
- [x] 独立只读审查 /root/read_path_review 完成,无阻塞发现;主代理负责修改与执行验证。
|
||||
- [x] 本地同一数据集三端点各 5 次 before/after 比较,全量响应一致。详情中位 1.7107→0.4841s;history 0.8580→0.0262s;rankings 1.2415→0.0645s。详见 research/performance.json,不能作为生产 SLA。
|
||||
- [x] Ruff format/check 通过;受改文件 Pyright 验证;全仓 Pyright 14 个 selection 错误与修改前文件/行/内容逐项完全一致。
|
||||
- [x] 后端全套以 unit→HTTP→integration 顺序执行:226 passed / 1 failed。剩余市场数据集成测试在修改前复现 Connection.executemany AttributeError;不扩大修改范围。默认集成测试优先顺序另有既存 Alembic fileConfig 污染 caplog 的问题。
|
||||
- [ ] 用户自行发布后复测公网 detail/history/rankings;无 Redis、无迁移、不要求历史重跑。
|
||||
|
||||
本地实现已完成。质量门禁存在明确的既有阻塞,未宣称全仓全绿;用户已授权将实现、回归测试和性能证据提交到本地 develop;任务待用户发布验证,暂不归档。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 接口性能诊断与缓存方案评估
|
||||
|
||||
## 目标
|
||||
解释用户观察到的多个接口约 3 秒延迟,以概念板块 BK1147.DC 在 2026-09-04 的详情接口为切入点,在不引入 Redis 的情况下优化冷请求读取与计算,供用户自行发布后评估效果。
|
||||
|
||||
## 已确认事实
|
||||
- 用户已在诊断与方案回顾后明确批准按优化顺序开始实施,由用户自行发布;Redis 留待上线效果验证后评估。
|
||||
- 公网 GET 四次均返回 HTTP 200,总耗时 3.045–3.442 秒,响应体 10750 字节;主要等待发生在首字节之前。
|
||||
- 排查开始时本地 develop 分支工作区干净;生产是否与当前提交一致尚未确认。
|
||||
- 静态检查及本地调用追踪确认:详情与金额榜单每次调用两次 load_publication_sources;来源查询读取完整 payload(application/details.py:145、:182、:294;infrastructure/postgres.py:194)。
|
||||
|
||||
## 范围与要求
|
||||
- 低频只读测量示例请求,分析本地路由、查询、连接管理与部署配置。
|
||||
- 区分观测事实、代码风险和待生产证据验证的假设。
|
||||
- 实施资金雷达详情、历史和榜单的最小充分读取优化;不修改外部系统。
|
||||
|
||||
## 验收标准
|
||||
- 详情/榜单/历史不再加载完整发布快照,按需读取来源组、目标日与成员;历史只传输目标板块排名及完整池统计。
|
||||
- 保留全部 HTTP 字段、Decimal 精度、旧批次独立缺失值、历史发布选择与相似板块口径。
|
||||
- 通过真实 PostgreSQL 查询验证、语义回归测试和后端质量门禁,给出同一数据集的前后性能比较;不承诺未上线的公网秒数。
|
||||
- 记录可复现请求的分段耗时,避免把总耗时直接等同于 SQL 耗时。
|
||||
- 为关键判断提供代码位置或实测依据。
|
||||
- 说明 Redis 是否必要、适用条件及失效策略边界。
|
||||
- 明确未验证事项及下一步建议。
|
||||
|
||||
## 不在范围内
|
||||
生产压测、部署、迁移、创建索引、接入 Redis、修改共享规范、推送远端。用户已另行授权提交本地 develop。
|
||||
@@ -0,0 +1,126 @@
|
||||
"""Task-local benchmark: isolated localhost PostgreSQL only; no production writes.
|
||||
|
||||
Run from zhixing-server with DATABASE_URL pointing to a disposable test database.
|
||||
Set PYTHONPATH to the before/after source tree to compare the same persisted data.
|
||||
Use --seed once, then --run before.json / --run after.json (five reads per endpoint).
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import runpy
|
||||
import statistics
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
from unittest.mock import patch
|
||||
|
||||
import psycopg
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from zhixing_server.bootstrap.config import Settings, sqlalchemy_database_url
|
||||
from zhixing_server.modules.sector_radar.application.details import ReadRadarDetails
|
||||
from zhixing_server.modules.sector_radar.application.read import ReadSectorRadar, RadarQuery
|
||||
from zhixing_server.modules.sector_radar.domain.models import PublicationStatus, SectorType, MetricKind, MetricUnit
|
||||
from zhixing_server.modules.sector_radar.domain.metrics import AmountNetStrategy, RatioTurnoverStrategy, SwingEqualThreeToTenStrategy
|
||||
from zhixing_server.modules.sector_radar.domain.persistence import PublicationSourceGroup as Group, PublicationSourceRecord, RankingRecord
|
||||
from zhixing_server.modules.sector_radar.domain.ranking import rank_metric_observations
|
||||
from zhixing_server.modules.sector_radar.domain.source import build_source_snapshot
|
||||
from zhixing_server.modules.sector_radar.infrastructure.postgres import PostgresSectorRadarRepository
|
||||
from zhixing_server.modules.sector_radar.presentation.http import _detail_response, _history_response, _rankings_response
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--seed', action='store_true')
|
||||
parser.add_argument('--run', type=Path)
|
||||
args = parser.parse_args()
|
||||
url = os.environ['DATABASE_URL']
|
||||
assert urlsplit(url).hostname in {'localhost', '127.0.0.1'}, 'disposable local database only'
|
||||
samples = runpy.run_path('tests/unit/sector_radar/test_detail_reads.py')
|
||||
target, now, code = samples['TARGET'], samples['NOW'], samples['CODE']
|
||||
repo = PostgresSectorRadarRepository(url)
|
||||
|
||||
if args.seed:
|
||||
config = Config('alembic.ini')
|
||||
config.set_main_option('sqlalchemy.url', sqlalchemy_database_url(url).replace('%', '%%'))
|
||||
config.config_file_name = None
|
||||
with patch('zhixing_server.bootstrap.config.get_settings', return_value=Settings(database_url=url)):
|
||||
command.upgrade(config, 'head')
|
||||
samples['seed_detail'](repo)
|
||||
current = repo.get_successful_publication(target)
|
||||
template = repo.load_rankings(current.publication_id)[0].observation
|
||||
stocks = [f'{i:06d}.SZ' for i in range(10, 3010)]
|
||||
sectors = [f'PERF{i:04d}.DC' for i in range(500)]
|
||||
def save(group, rows, order, day=target):
|
||||
snapshot = build_source_snapshot(api_name=group.value, params={'batch': str(order)}, rows=rows,
|
||||
target_trade_date=day, observed_at=now)
|
||||
repo.save_source_snapshots((snapshot,))
|
||||
repo.save_publication_sources((PublicationSourceRecord(current.publication_id, group, order, snapshot),))
|
||||
save(Group.STOCK_BASICS, tuple({'ts_code': s, 'symbol': s[:6], 'name': s, 'exchange': 'SZSE',
|
||||
'list_status': 'L', 'list_date': '20200101'} for s in stocks), 1)
|
||||
save(Group.CONCEPT_INDICES, tuple({'ts_code': s, 'name': s, 'trade_date': str(target),
|
||||
'pct_change': '1.1234'} for s in sectors), 1)
|
||||
save(Group.MEMBERS, tuple({'ts_code': sector, 'con_code': stocks[(i*7+j)%len(stocks)],
|
||||
'name': stocks[(i*7+j)%len(stocks)], 'trade_date': str(target)}
|
||||
for i, sector in enumerate(sectors) for j in range(100)), 1)
|
||||
for offset in range(10):
|
||||
day = target-timedelta(days=offset)
|
||||
for group in (Group.DAILY, Group.MONEYFLOW_DC, Group.MONEYFLOW):
|
||||
rows = tuple({'ts_code': stock, 'trade_date': str(day), 'name': stock,
|
||||
'pct_chg': str(Decimal(i % 123)/100), 'amount': str(i*12),
|
||||
'net_amount': str(i-1500), 'net_mf_amount': str(i-500),
|
||||
'close': str(Decimal(i%1000)/10+1), 'vol': str(i*33)}
|
||||
for i, stock in enumerate(stocks))
|
||||
save(group, rows, offset+1, day)
|
||||
versions = ((MetricKind.AMOUNT, AmountNetStrategy.metric_version, MetricUnit.CNY_100M),
|
||||
(MetricKind.RATIO, RatioTurnoverStrategy.metric_version, MetricUnit.RATIO),
|
||||
(MetricKind.SWING, SwingEqualThreeToTenStrategy.metric_version, MetricUnit.RATIO))
|
||||
for offset in range(30):
|
||||
day = target-timedelta(days=offset)
|
||||
if offset < 2:
|
||||
publication = repo.get_successful_publication(day)
|
||||
else:
|
||||
running = replace(current, publication_id=f'bench-{offset}', target_trade_date=day,
|
||||
status=PublicationStatus.RUNNING, input_hash=None, finished_at=None)
|
||||
repo.create_publication(running)
|
||||
publication = replace(running, status=PublicationStatus.SUCCESS, input_hash='a'*64,
|
||||
finished_at=now+timedelta(seconds=1))
|
||||
repo.finish_publication(publication)
|
||||
rankings = []
|
||||
for kind, version, unit in versions:
|
||||
observations = tuple(replace(template, trade_date=day, sector_code=sector, sector_name=sector,
|
||||
metric_kind=kind, metric_version=version, unit=unit,
|
||||
value=Decimal(i+1)) for i, sector in enumerate(sectors))
|
||||
rankings.extend(RankingRecord(publication.publication_id, row)
|
||||
for row in rank_metric_observations(observations))
|
||||
repo.save_rankings(rankings)
|
||||
with psycopg.connect(url) as connection:
|
||||
connection.execute('ANALYZE')
|
||||
print('dataset', connection.execute('SELECT count(*), sum(row_count), sum(octet_length(payload::text)) FROM sector_radar_source_snapshot').fetchone(),
|
||||
'rankings', connection.execute('SELECT count(*) FROM sector_radar_ranking').fetchone())
|
||||
|
||||
if args.run:
|
||||
repo.open()
|
||||
reads = {
|
||||
'detail': lambda: _detail_response(ReadRadarDetails(repo).detail(target, SectorType.CONCEPT, code)),
|
||||
'history': lambda: _history_response(ReadRadarDetails(repo).history(target, SectorType.CONCEPT, code)),
|
||||
'rankings': lambda: _rankings_response(ReadSectorRadar(repo).query(RadarQuery(trade_date=target, page_size=20))),
|
||||
}
|
||||
report = {}
|
||||
for label, read in reads.items():
|
||||
timings = []
|
||||
responses = []
|
||||
for _ in range(5):
|
||||
start = time.perf_counter()
|
||||
response = read().model_dump(mode='json')
|
||||
timings.append(time.perf_counter()-start)
|
||||
responses.append(response)
|
||||
assert all(item == responses[0] for item in responses)
|
||||
fingerprint = hashlib.sha256(json.dumps(responses[0], sort_keys=True).encode()).hexdigest()
|
||||
report[label] = {'seconds': timings, 'median_seconds': statistics.median(timings),
|
||||
'response_sha256': fingerprint, 'response': responses[0]}
|
||||
print(label, 'median', report[label]['median_seconds'], 'sha256', fingerprint, flush=True)
|
||||
args.run.write_text(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
repo.close()
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"environment": "isolated local PostgreSQL 16-alpine, Python 3.12.11, psycopg 3.3.4; same data, sequential requests, warm database/connection pool, no application cache",
|
||||
"baseline_commit": "e567e5f",
|
||||
"dataset": {
|
||||
"snapshot_count": 41,
|
||||
"source_rows": 143521,
|
||||
"source_json_bytes": 22785765,
|
||||
"ranking_rows": 45003
|
||||
},
|
||||
"samples_per_endpoint": 5,
|
||||
"measurements": {
|
||||
"detail": {
|
||||
"before_seconds": [
|
||||
1.7919143750004878,
|
||||
1.7288594170004217,
|
||||
1.7106771250000747,
|
||||
1.7057415830004174,
|
||||
1.6912810410012753
|
||||
],
|
||||
"after_seconds": [
|
||||
0.6212627920012892,
|
||||
0.4883682920008141,
|
||||
0.44150824999996985,
|
||||
0.43969708400072705,
|
||||
0.4841277909999917
|
||||
],
|
||||
"before_median_seconds": 1.7106771250000747,
|
||||
"after_median_seconds": 0.4841277909999917,
|
||||
"reduction_percent": 71.7,
|
||||
"responses_identical": true,
|
||||
"response_sha256": "d2d5693152e949767a2db8e807852fe6fd2cb6ec175664a5dab9f615cdda4467"
|
||||
},
|
||||
"history": {
|
||||
"before_seconds": [
|
||||
0.8467425830003776,
|
||||
0.8580366249989311,
|
||||
0.8756502500000352,
|
||||
0.8701953330000833,
|
||||
0.8388115420002578
|
||||
],
|
||||
"after_seconds": [
|
||||
0.026230833000226994,
|
||||
0.024217250000219792,
|
||||
0.025365250001414097,
|
||||
0.028609290999156656,
|
||||
0.026671499999793014
|
||||
],
|
||||
"before_median_seconds": 0.8580366249989311,
|
||||
"after_median_seconds": 0.026230833000226994,
|
||||
"reduction_percent": 96.9,
|
||||
"responses_identical": true,
|
||||
"response_sha256": "edf098e1f124a6d5526d9f202b67c6e97986095a6cb18fad3d0d6ba07d10d387"
|
||||
},
|
||||
"rankings": {
|
||||
"before_seconds": [
|
||||
1.3065002089988411,
|
||||
1.2318050410012802,
|
||||
1.2226990420003858,
|
||||
1.3149193330009439,
|
||||
1.2414632910004002
|
||||
],
|
||||
"after_seconds": [
|
||||
0.06522883400066348,
|
||||
0.06445087500105728,
|
||||
0.06437025000013818,
|
||||
0.06701250000151049,
|
||||
0.06281666699942434
|
||||
],
|
||||
"before_median_seconds": 1.2414632910004002,
|
||||
"after_median_seconds": 0.06445087500105728,
|
||||
"reduction_percent": 94.8,
|
||||
"responses_identical": true,
|
||||
"response_sha256": "dbe90217c0086e22a570990062e08583c44d72ca1b4cf87b39208b033ce12e62"
|
||||
}
|
||||
},
|
||||
"query_plan_samples": [
|
||||
{
|
||||
"query": "source_rows",
|
||||
"execution_ms": 31.641,
|
||||
"planning_ms": 1.534,
|
||||
"rows": 3
|
||||
},
|
||||
{
|
||||
"query": "rank_history",
|
||||
"execution_ms": 23.911,
|
||||
"planning_ms": 0.664,
|
||||
"rows": 90
|
||||
}
|
||||
],
|
||||
"validation": {
|
||||
"pytest_passed": 226,
|
||||
"pytest_failed_baseline": 1,
|
||||
"baseline_failure": "test_market_data_repository_pool.py:65 Connection.executemany AttributeError, reproduced before edits",
|
||||
"pyright_baseline_errors": 14,
|
||||
"pyright_new_errors": 0,
|
||||
"ruff": "passed",
|
||||
"review_agent": "/root/read_path_review complete: no blocking SQL/data correctness findings",
|
||||
"caveat": "Default integration-first ordering also disables caplog loggers through pre-existing Alembic fileConfig. Full tests were run unit/HTTP/integration order with both DB environment variables set; one baseline integration failure remained."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "api-performance-diagnosis",
|
||||
"name": "api-performance-diagnosis",
|
||||
"title": "接口性能诊断与缓存方案评估",
|
||||
"description": "优化资金雷达详情、历史与榜单读取;无 Redis、无迁移,待用户发布验证。",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "yuxuanhui",
|
||||
"assignee": "yuxuanhui",
|
||||
"createdAt": "2026-09-07",
|
||||
"completedAt": "2026-09-25",
|
||||
"branch": "develop",
|
||||
"base_branch": "develop",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "用户已授权提交本地 develop。226 项测试通过;1 个既有集成测试失败和 14 个既有类型错误均在修改前复现。",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user