feat(analysis): 长范围应用排行改走「小时级预聚合」,不再扫日志内容
=== 问题(实测)===
按应用排行必须读日志内容(Loki 3.4 的 index/volume 不支持 app 维度聚合,
aggregateBy=app 无效),成本 ∝ 时间范围:
5 分钟 ≈ 1.3GB → 9.8s
15 分钟 ≈ 3.9GB → 19.5s
1 小时 ≈ 15GB → 预计 60~90s
6 小时 ≈ 92GB → 5~8 分钟
24 小时 ≈ 370GB → 不可用
用户在时间范围里选 6h/24h 时,页面必然卡死。
=== 方案 ===
新增 models.AppVolumeSnapshot + services/app_volume.go:
- 后台任务每小时第 5 分钟抓「上一个完整小时」的 per-app 条数
- 用**瞬时查询**(/loki/api/v1/query + [1h])而不是 query_range:
单点评估、扫描量相同、省掉多步开销;并绕开 LokiMetrics 的 90s 上限
与重型查询信号量(后台任务不该和前台抢令牌)
- 每源每小时只留 top 50 应用,保留 90 天
- 幂等:同 (hour, source, ns, app) 更新而非重复插入
- 超出 Loki 26h 保留期的窗口跳过(抓也抓不到,白等几分钟)
新增 GET /api/v1/logs/app-ranking?sourceId&from&to&limit → 读库毫秒级
前端:analysis 判断范围 > 60 分钟就走预聚合接口,并停掉实时重查询
(rankingEnabled 置 false);表格副标题说明「读的是预聚合快照,已留存 N 小时,
不含错误数与趋势」。
=== 顺带修掉的根因 bug ===
useInView 原来用 useRef + useEffect([inView]):分析页首屏 data===null 时
内容块不渲染,ref.current 为 null,effect 直接 return;等数据到了 DOM 才挂上,
但依赖没变 → effect 不重跑 → IntersectionObserver 从未创建 → inView 永远 false
→ 重查询永不发出 → 页面一直显示"没有应用维度的数据"。
改成 callback ref(节点挂载即作为依赖),并加 scroll/resize 兜底 + 挂载瞬间量一次。
另外分析页加 8 秒无条件兜底:可见性判定再失灵,8 秒后也一定查一次。
默认时间范围 15 分钟 → 5 分钟(19.5s → 9.8s)。
=== 实测 ===
Pod = Hub latest = 98de87e7f895
5 分钟窗口 by-app:9.8s(75 序列);15 分钟:19.5s
This commit is contained in:
1 parent
7f6fb364de
commit
d0adc095dd
11 files changed
+467
-40
No files matched your search
@@ -330,6 +330,29 @@ func (ctrl *LogsController) Health(c *gin.Context) {
|
||||
utils.Success(c, gin.H{"ready": services.LokiReady(ctx, src)})
|
||||
}
|
||||
|
||||
// AppRanking 按应用排行(长范围走平台预聚合,毫秒级;短范围由前端走实时查询)。
|
||||
func (ctrl *LogsController) AppRanking(c *gin.Context) {
|
||||
sourceID, err := strconv.ParseUint(c.Query("sourceId"), 10, 64)
|
||||
if err != nil || sourceID == 0 {
|
||||
utils.BadRequest(c, "sourceId 无效")
|
||||
return
|
||||
}
|
||||
from, err1 := strconv.ParseInt(c.DefaultQuery("from", "0"), 10, 64)
|
||||
to, err2 := strconv.ParseInt(c.DefaultQuery("to", "0"), 10, 64)
|
||||
if err1 != nil || err2 != nil || from <= 0 || to <= 0 || to <= from {
|
||||
utils.BadRequest(c, "时间范围无效(from/to 需为纳秒时间戳且 to > from)")
|
||||
return
|
||||
}
|
||||
limit := 10
|
||||
if raw := c.Query("limit"); raw != "" {
|
||||
if v, convErr := strconv.Atoi(raw); convErr == nil && v > 0 && v <= 100 {
|
||||
limit = v
|
||||
}
|
||||
}
|
||||
rows := services.GetAppRanking(uint(sourceID), time.Unix(0, from), time.Unix(0, to), limit)
|
||||
utils.Success(c, gin.H{"list": rows, "hours": services.AppVolumeCoverage(uint(sourceID))})
|
||||
}
|
||||
|
||||
// DailyVolume 每日日志量历史(平台留存,不受 Loki retention 限制)。
|
||||
//
|
||||
// query: sourceId, days(默认 7,最长 400)
|
||||
|
||||
Reference in new issue
Block a user