mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 08:06:37 +08:00
perf(log): optimize PG log store queries and expired partition cleanup
- Count/节点访问日志统计改为单次扫描聚合,WAF 按 IP 聚合由三次扫描合并为两次 - IPSummaries 归属地改为取过滤窗口内最新记录(对齐 ClickHouse argMax 口径), 子查询带窗口条件,可分区裁剪并命中索引 - 新增 goose 迁移:of_node_access_logs (logged_at DESC, id DESC) 前导索引与 lower(trim(host)) 表达式索引,加速列表排序与主机过滤 - 过期日志清理先按数据校验直接 DROP 完全过期整月分区,再对边界月逐行删除; 启动时兜底预建当月及未来 2 个月分区,跨月停机重启后首次写入不再报 "no partition of relation found"
This commit is contained in:
@@ -803,6 +803,76 @@ func TestGormWAFAndIPSummaries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGormIPSummariesRegionWithinFilterWindow 验证 IPSummaries 的 region 取过滤窗口内该 IP
|
||||
// 最近一条(对齐 CH argMax(region, logged_at)):窗口外更新的 region 记录不参与,
|
||||
// 旧实现(子查询不带窗口条件)会错误返回窗口外那条。
|
||||
func TestGormIPSummariesRegionWithinFilterWindow(t *testing.T) {
|
||||
ResetForTest()
|
||||
SetConfigReader(func(_ context.Context, _ string) (string, error) { return "", nil })
|
||||
s := newTestGormStore(t)
|
||||
ctx := context.Background()
|
||||
base := time.Now().UTC().Truncate(time.Minute)
|
||||
rows := []analyticsmodel.NodeAccessLog{
|
||||
// 窗口外(晚于 Until):同一 IP 的更新 region,旧实现会误取。
|
||||
{ID: 1, NodeID: "n1", LoggedAt: base.Add(3 * time.Hour), RemoteAddr: "1.1.1.1", Region: "outside-new", StatusCode: 200},
|
||||
// 窗口内 [base, base+2h) 最新一条:region 应为 inside-old。
|
||||
{ID: 2, NodeID: "n1", LoggedAt: base.Add(time.Hour), RemoteAddr: "1.1.1.1", Region: "inside-old", StatusCode: 200},
|
||||
// 窗口内更早的一条,不应覆盖窗口内最新 region。
|
||||
{ID: 3, NodeID: "n1", LoggedAt: base, RemoteAddr: "1.1.1.1", Region: "inside-earlier", StatusCode: 200},
|
||||
}
|
||||
if err := s.BatchInsertNodeAccessLogs(ctx, rows); err != nil {
|
||||
t.Fatalf("insert: %v", err)
|
||||
}
|
||||
sums, err := s.IPSummaries(ctx, model.OpenFlareAccessLogQuery{
|
||||
NodeID: "n1",
|
||||
Since: base,
|
||||
Until: base.Add(2 * time.Hour),
|
||||
}, time.Time{})
|
||||
if err != nil {
|
||||
t.Fatalf("ip summaries: %v", err)
|
||||
}
|
||||
if len(sums) != 1 || sums[0].RemoteAddr != "1.1.1.1" || sums[0].Region != "inside-old" {
|
||||
t.Fatalf("ip summaries = %+v, want 1.1.1.1 region inside-old (window-external outside-new excluded)", sums)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGormIPSummariesEmptyFilter 覆盖 IPSummaries 空过滤分支(cond=="" 时 region 子查询
|
||||
// 不带参数、Select 走无 args 路径):OpenFlareAccessLogQuery{} 不报错、返回全部 IP 分组,
|
||||
// region 为各 IP 全部行中最新一条(无窗口即全部行)。
|
||||
func TestGormIPSummariesEmptyFilter(t *testing.T) {
|
||||
ResetForTest()
|
||||
SetConfigReader(func(_ context.Context, _ string) (string, error) { return "", nil })
|
||||
s := newTestGormStore(t)
|
||||
ctx := context.Background()
|
||||
base := time.Now().UTC().Truncate(time.Minute)
|
||||
rows := []analyticsmodel.NodeAccessLog{
|
||||
{ID: 1, NodeID: "n1", LoggedAt: base, RemoteAddr: "1.1.1.1", Region: "cn", StatusCode: 200},
|
||||
{ID: 2, NodeID: "n1", LoggedAt: base.Add(time.Minute), RemoteAddr: "1.1.1.1", Region: "us", StatusCode: 200},
|
||||
{ID: 3, NodeID: "n2", LoggedAt: base, RemoteAddr: "2.2.2.2", Region: "jp", StatusCode: 404},
|
||||
}
|
||||
if err := s.BatchInsertNodeAccessLogs(ctx, rows); err != nil {
|
||||
t.Fatalf("insert: %v", err)
|
||||
}
|
||||
sums, err := s.IPSummaries(ctx, model.OpenFlareAccessLogQuery{}, time.Time{})
|
||||
if err != nil {
|
||||
t.Fatalf("ip summaries empty filter: %v", err)
|
||||
}
|
||||
if len(sums) != 2 {
|
||||
t.Fatalf("ip summaries = %d groups, want 2", len(sums))
|
||||
}
|
||||
byIP := map[string]analyticsmodel.NodeAccessLogIPSummary{}
|
||||
for _, x := range sums {
|
||||
byIP[x.RemoteAddr] = x
|
||||
}
|
||||
// 无窗口即全部行:1.1.1.1 最新一条 region 为 us(region 子查询不带参数路径)。
|
||||
if byIP["1.1.1.1"].Region != "us" || byIP["1.1.1.1"].TotalRequests != 2 {
|
||||
t.Fatalf("ip summaries 1.1.1.1 = %+v, want region us requests 2", byIP["1.1.1.1"])
|
||||
}
|
||||
if byIP["2.2.2.2"].Region != "jp" || byIP["2.2.2.2"].TotalRequests != 1 {
|
||||
t.Fatalf("ip summaries 2.2.2.2 = %+v, want region jp requests 1", byIP["2.2.2.2"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestGormListRejectsUnsupportedSortBy 验证 List 对不支持的 SortBy 直接报错,
|
||||
// 默认 logged_at 路径与 CH 支持的 status_code/remote_addr 正常可用。
|
||||
func TestGormListRejectsUnsupportedSortBy(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user