- 流式放行前的上游错误判定加廉价子串预过滤("error" / response.failed): 长思考阶段几十上百条事件不再逐条 json.Unmarshal。 - usage_logs 去掉 idx_channel_created:没有任何查询按 channel_id 过滤该表 (渠道统计走 created_at 时间窗后在内存分组),复合索引只是每行一次多余的 B-tree 插入;schema v9 迁移里显式 DROP 旧库索引。至此该表只剩 (user_id, created_at)、created_at 两个真被用到的索引。 - 新增可选保留期(OT_USAGE_RETENTION_DAYS,默认 0=永久保留):每天分批 (2000 行/批、批间 50ms)清理过期 usage_logs,避免长期运行后时间窗查询 与备份随表增长变慢;余额流水 balance_logs 不在清理范围。 参数复测(记账稳态吞吐,3 次取中位): - batch 64(当前)30.2k rows/s;batch 128 19.0k、batch 256 14.3k(更差,保持 64) - wal_autocheckpoint(4000)、cache_size(-64MB) 与当前配置无差别,不引入 验证:go vet、go test ./...、-race(usage/proxy/store)全绿;新增保留期三项测试 (只删明细不动流水、未开启不动数据、跨分批删完)。
87 lines
2.1 KiB
Go
87 lines
2.1 KiB
Go
package usage
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// 记账明细的保留期清理。
|
|
//
|
|
// usage_logs 只增不减:长期运行的实例上,渠道统计这类「按时间窗扫描」的查询会
|
|
// 随表增长线性变慢,备份也会越来越大。余额流水(balance_logs)不在清理范围内——
|
|
// 那是财务记录,需要长期保留。
|
|
//
|
|
// 默认关闭(retention_days=0):删数据是不可逆操作,由部署者显式开启。
|
|
// 清理分批进行(每批 2000 行、批间小睡),避免长时间持锁影响在线请求。
|
|
|
|
const (
|
|
retentionBatch = 2000
|
|
retentionTick = 24 * time.Hour
|
|
)
|
|
|
|
// StartRetention 周期性删除超过 days 天的用量明细;days<=0 时不启动。
|
|
// 返回停止函数(进程退出时调用)。
|
|
func StartRetention(ctx context.Context, db *gorm.DB, days int) (stop func()) {
|
|
if days <= 0 || db == nil {
|
|
return func() {}
|
|
}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
purgeOlderThan(ctx, db, days)
|
|
t := time.NewTicker(retentionTick)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-done:
|
|
return
|
|
case <-t.C:
|
|
purgeOlderThan(ctx, db, days)
|
|
}
|
|
}
|
|
}()
|
|
return func() {
|
|
select {
|
|
case <-done:
|
|
default:
|
|
close(done)
|
|
}
|
|
}
|
|
}
|
|
|
|
// purgeOlderThan 分批删除 cutoff 之前的明细,返回删除行数。
|
|
func purgeOlderThan(ctx context.Context, db *gorm.DB, days int) int64 {
|
|
cutoff := time.Now().UTC().AddDate(0, 0, -days)
|
|
var total int64
|
|
for {
|
|
if ctx.Err() != nil {
|
|
return total
|
|
}
|
|
res := db.Exec(
|
|
"DELETE FROM usage_logs WHERE id IN (SELECT id FROM usage_logs WHERE created_at < ? LIMIT ?)",
|
|
cutoff, retentionBatch)
|
|
if res.Error != nil {
|
|
log.Printf("usage: retention purge failed: %v", res.Error)
|
|
return total
|
|
}
|
|
n := res.RowsAffected
|
|
total += n
|
|
if n < retentionBatch {
|
|
break
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return total
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
}
|
|
if total > 0 {
|
|
log.Printf("usage: retention removed %d rows older than %s", total, cutoff.Format("2006-01-02"))
|
|
}
|
|
return total
|
|
}
|