fix(alerts): deterministic tiebreaker for paginated alert list

list_alerts ordered by created_at DESC alone; kpi_alerts has many rows sharing the same second timestamp (07:00 batches), so MySQL tie order is unstable and offset pagination both duplicated and skipped rows (entity 1 read-only probe: total=281, 281 rows walked, only 268 unique ids, 13 duplicates; id 587 unreachable in the unfiltered list while visible in ?status=pending).

Add secondary key id DESC so pages form a total order; add regression suite test_alerts_pagination_stability_20260925.py (tie-heavy fixture: unique==total, zero duplicates, strict id DESC inside a tie group, page concat == single-shot ordered query, unfiltered vs status-filtered visibility, entity isolation negative control).
This commit is contained in:
Hermes CI Fix
2026-09-25 14:06:38 +08:00
parent a8391a03da
commit e3695088a4
2 changed files with 176 additions and 1 deletions
+7 -1
View File
@@ -23,7 +23,13 @@ def list_alerts(status: str = None, page: int = Query(1, ge=1), db: Session = De
if status:
query = query.filter(KPIAlert.status == status)
total = query.count()
alerts = query.order_by(KPIAlert.created_at.desc()).offset((page-1)*20).limit(20).all()
# 分页确定性(2026-09-25 alerts-list-pagination-skip):原为 created_at.desc() **单键** +
# offset/limit — kpi_alerts 每批 07:00 生成、**大量行同秒 created_at**,MySQL 并列组内顺序
# 不确定 ⇒ 偏移分页重复并漏行(entity 1 只读实测:total=281 / 逐页取满 281 行 / 唯一 id 仅 268 /
# 重复 13;id=587 在无过滤列表不可达,而同行 ?status=pending 首屏正常可见)。
# 补**确定性次键** id.desc()(同秒内后写入者在前)→ 全序分页,逐页遍历可覆盖全部 total 行。
alerts = (query.order_by(KPIAlert.created_at.desc(), KPIAlert.id.desc())
.offset((page-1)*20).limit(20).all())
return {"total": total, "data": [{c.name: getattr(a, c.name) for c in KPIAlert.__table__.columns} for a in alerts]}
@router.post("/{alert_id}/resolve")