fix(daily-volume): 30/90/365 天切换"没反应";/logs/stats 加短缓存
=== 1. 切换范围没反应(真 bug)===
现象:7/30/90/365 天切换后图完全一样。
根因:GetDailyVolumeHistory 只返回**已抓到的快照**。平台只有 4 天数据,
所以任何 days 都只返回 4 个点 → 图表一模一样。
修复:始终返回 days 个点,缺失的天用 Missing=true 标记。
实测:days=7→7 点(缺3)、30→30 点(缺26)、90→90(缺86)、365→365(缺361)。
为什么必须区分 Missing 和"0 字节":
前者是"平台没留存",后者是"那天确实没日志"。
混在一起会让 30/90 天的图上出现大片 0,被误读成"日志量暴跌/系统停摆"。
前端:缺失的天画灰色浅柱,tooltip 显示"未留存",图下方提示
"其中 N 天平台没有留存记录(灰色柱),不是当天没有日志"。
=== 2. /logs/stats 加 90 秒缓存 ===
分析页每次进入/切范围都打 index/stats,实测 5 分钟窗口冷查询 1.2 秒
(60 分钟更慢)。这类聚合值可容忍分钟级陈旧,加与指标查询一致的缓存:
90s TTL + 时间窗按 2 分钟取整 + 上限 200 条 + 过期清理
实测:重复请求 1227ms → 8ms。
=== 3. 顺带确认已生效的优化 ===
histogram(24h) : 54ms(volume_range)
daily-volume : 10ms(只读库)
stats 二次请求 : 8ms(新缓存)
长范围(>1h) : 不跑 count_over_time(预聚合快照 + 跳过内容扫描)
Pod = Hub latest = 12453ea723c1
This commit is contained in:
1 parent
5c7a3ec81d
commit
7b1ffd0274
7 files changed
+118
-26
No files matched your search
@@ -701,6 +701,61 @@ func LokiMetrics(ctx context.Context, src *models.LogSource, query string, start
|
||||
return series, nil
|
||||
}
|
||||
|
||||
// ── 总条数/总体积的短缓存 ─────────────────────────────────────────────
|
||||
// /logs/stats 走 index/stats,实测 5 分钟窗口要 1.2 秒(60 分钟更慢)。
|
||||
// 分析页每次进页面、每次切时间范围都会打它,而这类聚合值的可容忍陈旧度
|
||||
// 与指标查询一致(分钟级),所以用同样的 90s TTL + 2 分钟取整。
|
||||
const (
|
||||
statsCacheTTL = 90 * time.Second
|
||||
statsCacheMax = 200
|
||||
)
|
||||
|
||||
type statsCacheEntry struct {
|
||||
stats *LokiStats
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
statsCacheMu sync.Mutex
|
||||
statsCache = map[string]statsCacheEntry{}
|
||||
)
|
||||
|
||||
// LokiStatsCached 带短缓存的总量统计。
|
||||
func LokiStatsCached(ctx context.Context, src *models.LogSource, query string, start, end int64) (*LokiStats, error) {
|
||||
key := ""
|
||||
if src != nil {
|
||||
bucket := int64(metricCacheBucket)
|
||||
key = fmt.Sprintf("%d|%s|%d|%d", src.ID, query, start/bucket, end/bucket)
|
||||
statsCacheMu.Lock()
|
||||
if entry, ok := statsCache[key]; ok && time.Now().Before(entry.expiresAt) {
|
||||
statsCacheMu.Unlock()
|
||||
return entry.stats, nil
|
||||
}
|
||||
statsCacheMu.Unlock()
|
||||
}
|
||||
|
||||
st, err := LokiStatsFor(ctx, src, query, start, end)
|
||||
if err != nil || key == "" {
|
||||
return st, err
|
||||
}
|
||||
|
||||
statsCacheMu.Lock()
|
||||
if len(statsCache) >= statsCacheMax {
|
||||
now := time.Now()
|
||||
for k, v := range statsCache {
|
||||
if now.After(v.expiresAt) {
|
||||
delete(statsCache, k)
|
||||
}
|
||||
}
|
||||
if len(statsCache) >= statsCacheMax {
|
||||
statsCache = map[string]statsCacheEntry{}
|
||||
}
|
||||
}
|
||||
statsCache[key] = statsCacheEntry{stats: st, expiresAt: time.Now().Add(statsCacheTTL)}
|
||||
statsCacheMu.Unlock()
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// lokiMetricsUncached 真正打 Loki 的实现(不带缓存)。
|
||||
func lokiMetricsUncached(ctx context.Context, src *models.LogSource, query string, start, end int64) ([]MetricSeries, error) {
|
||||
if end <= 0 {
|
||||
|
||||
Reference in new issue
Block a user