fix(stats): 今日计数不再被每日缓存冻结,日期分组不依赖库会话时区

自查部署结果时发现两处正确性问题,都是本页上线时引入的:

1. 「今日提问 / 今日回答」原本随整页聚合一整天缓存,页面却写着「实时」,
   实际当天第一次访问后数字就冻住到次日。现拆出 TodayCounts 单独取,
   60 秒 TTL、跨日按日期失效;命中整页缓存时返回副本,不原地改缓存指针
   (那会被并发读踩成数据竞争)。

2. 日/月/时分组用 to_char/EXTRACT 直接作用在 timestamptz 上,结果跟着数据库
   会话时区走,而 PG 官方镜像默认 UTC、Go 侧按容器时区(Asia/Shanghai)切今天。
   实测同一条 2026-10-24 01:30+08 的提问,UTC 会话下被算成 10-23 17 点 ——
   日报表整体偏 8 小时、热力图时段错位,且与「今日」对不上。
   现统一经 analyticsLocalize() 加 AT TIME ZONE INTERVAL 换算,偏移取自
   time.Time.Zone()(Go 未设 TZ 时给 "Local",PG 不认该名,故不用 IANA 名)。
   过滤条件仍是裸列,索引照用。

另外两处顺手修:

- PeakDay 用 >= 比较,并列时会漂到最近的同值日期,与 busiestSlot 的取值规则不一致。
- 聚合改用 context.WithoutCancel + 30s 超时:第一个访客在聚合途中关掉页面时,
  排在后面等锁的请求不会各自重跑一遍全表 GROUP BY。

测试:新增 TestTodayNumbersNotFrozen(缓存命中后今日数仍更新、整页聚合当天只算一次)
与 TestTodayCacheExpiresAcrossMidnight;analytics 连真库通过。
This commit is contained in:
tamakyi
2026-10-24 01:20:02 +08:00
parent 64436d9d61
commit 0c282914c2
6 changed files with 294 additions and 43 deletions
+6 -3
View File
@@ -69,9 +69,12 @@ func TestSiteAnalytics(t *testing.T) {
require.EqualValues(t, 1, got.ActiveBoxes)
require.EqualValues(t, 1, got.AnsweringBoxes)
// 今日(提问是刚才写的,必然落在今天)
require.EqualValues(t, 2, got.TodayQuestions)
require.EqualValues(t, 1, got.TodayAnswers)
// 今日(提问是刚才写的,必然落在今天)—— 单独取,不随整页聚合缓存
dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
todayQ, todayA, err := store.TodayCounts(ctx, dayStart)
require.NoError(t, err)
require.EqualValues(t, 2, todayQ)
require.EqualValues(t, 1, todayA)
// 日/月趋势里能找到今天与本月
today := now.Format("2006-01-02")