perf(api): 会话/5数字弹窗超时修复——日粒度汇总层 msg_daily_stats

根因(服务器实测):sessions 对 owner 全量 message GROUP BY,重账号 5.2万条
3~5s;staff-chat-stats 全时段窗 6.9s——128MB buffer pool 下被推过前端
15s 超时(ECONNABORTED)。EXPLAIN ANALYZE 证实最优 covering 索引也要
18.9s,瓶颈是磁盘不是查询形状。

修复(预聚合,云客同款思路):
- msg_daily_stats 表:每「渠道×账号×会话×上海自然日」一行(收发计数/
  当日最后时间与方向);迁移 a7d3e9f1c2b4(幂等建表)
- 写路径:repos.upsert_messages 落库自动重算本批触及的会话×天(所有
  抓取路径汇到该函数,一处挂全;失败不阻塞写入)
- 读路径:sessions/staff-chat-stats 表有行时读汇总(namedtuple 同形,
  端点主体零改动),空表守卫回退旧聚合(部署回填前不空白)
- 自愈:CLI daily-stats 全量重建(幂等);full_sync_all.sh 夜间追加第 11
  步——顺带修掉尾部 sqlite3 直连 smoke.db 的硬编码(服务器 MySQL 上会炸)
- 口径统一:day 一律 Asia/Shanghai Python 计算(旧 from_unixtime 跟随
  DB 会话时区与今日数割裂);统计窗口对齐自然日(文档化语义变化)

测试:12 新增(汇总层 8 + 端点判别/对拍 4,判别手法=回填后删光 message
仍出完整数据),全量 512 绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-03 16:37:04 +08:00
parent e3f7aa1304
commit 421998be54
11 changed files with 892 additions and 32 deletions
+41
View File
@@ -740,6 +740,46 @@ class SyncRun(Base):
__table_args__ = (Index("ix_sync_run_entity_status", "entity_type", "status"),)
class MsgDailyStats(Base):
"""日粒度会话统计:每个「渠道×账号×会话×自然日」一行(2026-09-03)。
sessions(staff-chat 会话列表)与 staff-chat-stats(5 数字弹窗)的预聚
合层——这两端点原先每次请求对 owner 全量 message 做 GROUP BY(重账号
5.2 万条实测 3~7s,128MB buffer pool 下更被推过前端 15s 超时)。日行由
写路径维护:``repos.upsert_messages`` 落库后自动重算本批触及的
「会话×天」;夜间 ``recompute_all`` 全量重建自愈(裸 UPDATE/回填脚本
等绕过钩子的写入由它兜底)。
口径:
- ``day`` 一律 Asia/Shanghai 自然日(Python 计算,不用 SQL 日期函数——
from_unixtime 跟随 MySQL 会话时区,UTC 容器下与今日数口径割裂)。
- ``send_cnt``/``recv_cnt`` 仅计 is_send 真/假;NULL 两边都不计(对齐
旧聚合的 case 语义)。
- ``last_ts``/``last_is_send`` 为该日最后一条消息的时间与方向。
"""
__tablename__ = "msg_daily_stats"
channel: Mapped[str] = mapped_column(String(16), primary_key=True)
owner_account_id: Mapped[str] = mapped_column(String(128), primary_key=True)
# friend = 1:1 私聊(peer=talker_id) / room = 群(peer=room_id)
peer_kind: Mapped[str] = mapped_column(String(8), primary_key=True)
peer_id: Mapped[str] = mapped_column(String(128), primary_key=True)
# yyyymmdd,如 20260903(上海自然日)
day: Mapped[int] = mapped_column(Integer, primary_key=True)
send_cnt: Mapped[int] = mapped_column(
Integer, nullable=False, default=0, server_default=sa.text("0")
)
recv_cnt: Mapped[int] = mapped_column(
Integer, nullable=False, default=0, server_default=sa.text("0")
)
# 当日最后一条消息(ms);当天无有效方向计数的消息也存在(is_send=NULL)
last_ts: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True)
last_is_send: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
updated_at: Mapped[dt.datetime] = _updated_at()
__all__ = [
"ChatGroup",
"Customer",
@@ -748,6 +788,7 @@ __all__ = [
"Media",
"Message",
"MomentsPost",
"MsgDailyStats",
"PhoneCall",
"Sms",
"Staff",