mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 14:26:36 +08:00
f530cd4025
- 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"
67 lines
2.5 KiB
Go
67 lines
2.5 KiB
Go
// Copyright 2026 Arctel.net
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
package logstore
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// listPartitionNames 列出 table 在当前 schema 下的全部直接分区表名(pg_inherits)。
|
||
func listPartitionNames(ctx context.Context, gdb *gorm.DB, table string) ([]string, error) {
|
||
var names []string
|
||
if err := gdb.WithContext(ctx).Raw(`
|
||
SELECT c.relname
|
||
FROM pg_inherits i
|
||
JOIN pg_class c ON c.oid = i.inhrelid
|
||
JOIN pg_class p ON p.oid = i.inhparent
|
||
JOIN pg_namespace n ON n.oid = p.relnamespace AND n.nspname = current_schema()
|
||
WHERE p.relname = ?`, table).Scan(&names).Error; err != nil {
|
||
return nil, fmt.Errorf("list partitions of %s: %w", table, err)
|
||
}
|
||
return names, nil
|
||
}
|
||
|
||
// DropExpiredPartitions 直接删除完全过期的 PG 整月分区(避免 retention 清理逐行 DELETE):
|
||
// 候选 = 月份早于 cutoff 月(按 cutoff 的 UTC 时刻取月,避免本地时区偏移超前误删)的分区,
|
||
// 且删除前校验分区内不存在 logged_at >= cutoff 的行(分区边界随会话时区偏移,
|
||
// 名称月份只能粗筛,必须以数据为准);仅处理 of_node_access_logs
|
||
// (w_user_access_logs 无 retention 清理,刻意不删其分区);迁移冻结期间(ensureWritable)
|
||
// 直接返回 ErrMigrating,避免对冻结源库整月 DROP 丢数据;CH/SQLite 为 no-op。
|
||
func (s *gormLogStore) DropExpiredPartitions(ctx context.Context, cutoff time.Time) error {
|
||
if !isPostgresDialect(s.db) {
|
||
return nil
|
||
}
|
||
if err := s.ensureWritable(ctx); err != nil {
|
||
return err
|
||
}
|
||
names, err := listPartitionNames(ctx, s.db, "of_node_access_logs")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
cu := cutoff.UTC()
|
||
cutoffMonth := time.Date(cu.Year(), cu.Month(), 1, 0, 0, 0, 0, time.UTC)
|
||
for _, name := range names {
|
||
month, ok := partitionNameMonth("of_node_access_logs", name)
|
||
if !ok || !month.Before(cutoffMonth) {
|
||
continue // 非法命名或当月/未来月分区,必须保留
|
||
}
|
||
// 数据校验:分区内仍有 logged_at >= cutoff 的行则保留(时区偏移下名称月份可能超前于真实边界)。
|
||
var hasRetained int
|
||
if err := s.db.WithContext(ctx).Raw("SELECT 1 FROM "+name+" WHERE logged_at >= ? LIMIT 1", cu).Scan(&hasRetained).Error; err != nil {
|
||
return fmt.Errorf("check partition %s retained rows: %w", name, err)
|
||
}
|
||
if hasRetained == 1 {
|
||
continue
|
||
}
|
||
if err := s.db.WithContext(ctx).Exec("DROP TABLE IF EXISTS " + name).Error; err != nil {
|
||
return fmt.Errorf("drop expired partition %s: %w", name, err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|