perf: 流式错误判定预过滤、再减一个写索引,加可选明细保留期
- 流式放行前的上游错误判定加廉价子串预过滤("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)全绿;新增保留期三项测试 (只删明细不动流水、未开启不动数据、跨分批删完)。
This commit is contained in:
1 parent
8e5fe16611
commit
531a03c836
9 files changed
+224
-20
No files matched your search
@@ -27,7 +27,8 @@ import (
|
||||
// idx_user_created 是 user_id 单列,光改 struct tag 不会重建,需删掉重迁一次。
|
||||
// v7:usage_logs 加 reasoning_tokens / cache_write_1h_tokens / served_model 三列。
|
||||
// v8:去掉 usage_logs 上没人查询的 key_id / trace_id 索引(每行两次多余的 B-tree 插入)。
|
||||
const schemaVersion = 8
|
||||
// v9:同样去掉没人查询的 idx_channel_created。
|
||||
const schemaVersion = 9
|
||||
|
||||
// Open 打开数据库连接并自动迁移。
|
||||
// 开发默认 SQLite(自动追加 WAL 与 busy_timeout,见 withSQLitePragmas),生产可切 postgres。
|
||||
@@ -137,7 +138,7 @@ func withSQLitePragmas(dsn string) string {
|
||||
// dropUnusedUsageLogIndexes 删除历史库里已经没用的索引:AutoMigrate 不会自动删索引,
|
||||
// 去掉 struct tag 只是不再新建,旧库上它们还会继续拖慢每次插入。
|
||||
func dropUnusedUsageLogIndexes(db *gorm.DB) {
|
||||
for _, name := range []string{"idx_usage_logs_key_id", "idx_usage_logs_trace_id"} {
|
||||
for _, name := range []string{"idx_usage_logs_key_id", "idx_usage_logs_trace_id", "idx_channel_created"} {
|
||||
if err := db.Exec("DROP INDEX IF EXISTS " + name).Error; err != nil {
|
||||
log.Printf("store: drop index %s: %v", name, err)
|
||||
}
|
||||
|
||||
@@ -187,10 +187,12 @@ type UsageLog struct {
|
||||
RequestID string `gorm:"size:128" json:"request_id"` // 上游 request id
|
||||
// TraceID/KeyID 不建索引:没有查询按它们过滤(幂等检查走 balance_logs.ref_id),
|
||||
// 每个索引都是每行一次 B-tree 插入,在记账这条最热的写路径上纯属写放大。
|
||||
TraceID string `gorm:"size:64" json:"trace_id"`
|
||||
UserID uint64 `gorm:"index:idx_user_created,priority:1;not null" json:"user_id"`
|
||||
KeyID uint64 `json:"key_id"`
|
||||
ChannelID uint64 `gorm:"index:idx_channel_created,priority:1" json:"channel_id"`
|
||||
TraceID string `gorm:"size:64" json:"trace_id"`
|
||||
UserID uint64 `gorm:"index:idx_user_created,priority:1;not null" json:"user_id"`
|
||||
KeyID uint64 `json:"key_id"`
|
||||
// 与 key_id/trace_id 同理:没有任何查询按 channel_id 过滤 usage_logs
|
||||
// (渠道统计走 created_at 时间窗后在内存里分组),复合索引纯属写放大。
|
||||
ChannelID uint64 `json:"channel_id"`
|
||||
ModelID uint64 `json:"model_id"`
|
||||
ModelName string `gorm:"size:128" json:"model_name"`
|
||||
Protocol string `gorm:"size:32" json:"protocol"` // responses|chat|messages
|
||||
@@ -210,8 +212,9 @@ type UsageLog struct {
|
||||
ErrorCode *string `json:"error_code,omitempty"`
|
||||
RawRequest string `gorm:"type:text" json:"raw_request"` // 客户端原始请求体(未转换)
|
||||
RawResponse string `gorm:"type:text" json:"raw_response"` // 上游原始响应(未转换;流式为全部 SSE 事件)
|
||||
// CreatedAt 同时参与 user/channel 两个复合索引(时间窗过滤),自身单列索引保留给全表时间扫
|
||||
CreatedAt time.Time `gorm:"index;index:idx_user_created,priority:2;index:idx_channel_created,priority:2" json:"created_at"`
|
||||
// CreatedAt 参与 (user, created) 复合索引(用户侧时间窗过滤),单列索引服务
|
||||
// 全表时间窗扫描(渠道统计、运营总览)。
|
||||
CreatedAt time.Time `gorm:"index;index:idx_user_created,priority:2" json:"created_at"`
|
||||
}
|
||||
|
||||
// UsageDaily 日粒度预聚合(PLANNING §6.6)
|
||||
|
||||
Reference in new issue
Block a user