Merge origin/main into fix/account-extra-scheduler-pressure-20260311

This commit is contained in:
ius
2026-03-12 11:12:01 +08:00
73 changed files with 5725 additions and 437 deletions

View File

@@ -626,29 +626,6 @@ func (r *accountRepository) syncSchedulerAccountSnapshot(ctx context.Context, ac
}
}
func (r *accountRepository) patchSchedulerAccountExtra(ctx context.Context, accountID int64, updates map[string]any) {
if r == nil || r.schedulerCache == nil || accountID <= 0 || len(updates) == 0 {
return
}
account, err := r.schedulerCache.GetAccount(ctx, accountID)
if err != nil {
logger.LegacyPrintf("repository.account", "[Scheduler] patch account extra read failed: id=%d err=%v", accountID, err)
return
}
if account == nil {
return
}
if account.Extra == nil {
account.Extra = make(map[string]any, len(updates))
}
for key, value := range updates {
account.Extra[key] = value
}
if err := r.schedulerCache.SetAccount(ctx, account); err != nil {
logger.LegacyPrintf("repository.account", "[Scheduler] patch account extra write failed: id=%d err=%v", accountID, err)
}
}
func (r *accountRepository) syncSchedulerAccountSnapshots(ctx context.Context, accountIDs []int64) {
if r == nil || r.schedulerCache == nil || len(accountIDs) == 0 {
return
@@ -1221,15 +1198,15 @@ func (r *accountRepository) UpdateExtra(ctx context.Context, id int64, updates m
if affected == 0 {
return service.ErrAccountNotFound
}
if shouldEnqueueSchedulerOutboxForExtraUpdates(updates) {
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventAccountChanged, &id, nil, nil); err != nil {
logger.LegacyPrintf("repository.account", "[SchedulerOutbox] enqueue extra update failed: account=%d err=%v", id, err)
}
} else {
// 观测型 extra 字段不需要触发 bucket 重建,但尽量把单账号缓存补到最新
// 让 sticky session / GetAccount 命中缓存时也能读到最新快照。
r.patchSchedulerAccountExtra(ctx, id, updates)
// 观测型 extra 字段不需要触发 bucket 重建,但仍同步单账号快照
// 让 sticky session / GetAccount 命中缓存时也能读到最新数据,
// 同时避免缓存局部 patch 覆盖掉并发写入的其它账号字段。
r.syncSchedulerAccountSnapshot(ctx, id)
}
return nil
}
@@ -1239,9 +1216,10 @@ func shouldEnqueueSchedulerOutboxForExtraUpdates(updates map[string]any) bool {
return false
}
for key := range updates {
if !isSchedulerNeutralExtraKey(key) {
return true
if isSchedulerNeutralExtraKey(key) {
continue
}
return true
}
return false
}
@@ -1262,6 +1240,82 @@ func isSchedulerNeutralExtraKey(key string) bool {
return false
}
func shouldSyncSchedulerSnapshotForExtraUpdates(updates map[string]any) bool {
return codexExtraIndicatesRateLimit(updates, "7d") || codexExtraIndicatesRateLimit(updates, "5h")
}
func codexExtraIndicatesRateLimit(updates map[string]any, window string) bool {
if len(updates) == 0 {
return false
}
usedValue, ok := updates["codex_"+window+"_used_percent"]
if !ok || !extraValueIndicatesExhausted(usedValue) {
return false
}
return extraValueHasResetMarker(updates["codex_"+window+"_reset_at"]) ||
extraValueHasPositiveNumber(updates["codex_"+window+"_reset_after_seconds"])
}
func extraValueIndicatesExhausted(value any) bool {
number, ok := extraValueToFloat64(value)
return ok && number >= 100-1e-9
}
func extraValueHasPositiveNumber(value any) bool {
number, ok := extraValueToFloat64(value)
return ok && number > 0
}
func extraValueHasResetMarker(value any) bool {
switch v := value.(type) {
case string:
return strings.TrimSpace(v) != ""
case time.Time:
return !v.IsZero()
case *time.Time:
return v != nil && !v.IsZero()
default:
return false
}
}
func extraValueToFloat64(value any) (float64, bool) {
switch v := value.(type) {
case float64:
return v, true
case float32:
return float64(v), true
case int:
return float64(v), true
case int8:
return float64(v), true
case int16:
return float64(v), true
case int32:
return float64(v), true
case int64:
return float64(v), true
case uint:
return float64(v), true
case uint8:
return float64(v), true
case uint16:
return float64(v), true
case uint32:
return float64(v), true
case uint64:
return float64(v), true
case json.Number:
parsed, err := v.Float64()
return parsed, err == nil
case string:
parsed, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
return parsed, err == nil
default:
return 0, false
}
}
func (r *accountRepository) BulkUpdate(ctx context.Context, ids []int64, updates service.AccountBulkUpdate) (int64, error) {
if len(ids) == 0 {
return 0, nil