Files
sub2api/backend/ent/schema/channel_monitor_history.go
erio 8cf83c984e feat(channel-monitor): aggregate history to daily rollups + soft delete
明细只保留 1 天,超过 1 天聚合到新表 channel_monitor_daily_rollups(按
monitor_id/model/bucket_date 维度),聚合保留 30 天。两张表都用 SoftDeleteMixin
软删除(DELETE 自动改为 UPDATE deleted_at = NOW())。

聚合 + 清理任务由 OpsCleanupService 的 cron 统一调度,与运维监控的清理共享
schedule(默认 0 2 * * *)和 leader lock。ChannelMonitorRunner 的 cleanupLoop
被移除,只保留 dueCheckLoop。

读取路径 ComputeAvailability* 改为 UNION 明细(今天 deleted_at IS NULL)+
聚合(过去 windowDays 天 deleted_at IS NULL),SUM(ok)/SUM(total) 自然加权
计算可用率,AVG latency 用 SUM(sum_latency_ms)/SUM(count_latency)。

watermark 表 channel_monitor_aggregation_watermark 单行(id=1),记录
last_aggregated_date,重启后从该日期 +1 继续聚合,首次为 nil 则从
today - 30d 开始回填,单次最多 35 天上限避免长事务。

raw SQL 的 ListLatestPerModel / ListLatestForMonitorIDs / ListRecentHistoryForMonitors
都补上 deleted_at IS NULL 过滤(SoftDeleteMixin interceptor 只对 ent query 生效)。

bump version to 0.1.114.28

GroupBadge 在 MonitorKeyPickerDialog 中复用平台主题色 + 倍率/专属倍率
(顺手优化)。
2026-04-21 10:10:56 +08:00

74 lines
1.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
)
// ChannelMonitorHistory holds the schema definition for the ChannelMonitorHistory entity.
// 渠道监控历史:每次检测每个模型一行记录。明细只保留 1 天,超过 1 天的数据被聚合到
// channel_monitor_daily_rollups 后软删deleted_at由后续懒清理任务物理移除。
type ChannelMonitorHistory struct {
ent.Schema
}
func (ChannelMonitorHistory) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "channel_monitor_histories"},
}
}
func (ChannelMonitorHistory) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.SoftDeleteMixin{},
}
}
func (ChannelMonitorHistory) Fields() []ent.Field {
return []ent.Field{
field.Int64("monitor_id"),
field.String("model").
NotEmpty().
MaxLen(200),
field.Enum("status").
Values("operational", "degraded", "failed", "error"),
field.Int("latency_ms").
Optional().
Nillable(),
field.Int("ping_latency_ms").
Optional().
Nillable(),
field.String("message").
Optional().
Default("").
MaxLen(500),
field.Time("checked_at").
Default(time.Now),
}
}
func (ChannelMonitorHistory) Edges() []ent.Edge {
return []ent.Edge{
edge.From("monitor", ChannelMonitor.Type).
Ref("history").
Field("monitor_id").
Unique().
Required(),
}
}
func (ChannelMonitorHistory) Indexes() []ent.Index {
return []ent.Index{
index.Fields("monitor_id", "model", "checked_at"),
index.Fields("checked_at"),
}
}