Jelajahi Sumber

🏎️ perf: optimize aggregated model look-ups by batching bound-channel queries

Summary
-------
1. **Backend**
   • `model/model_meta.go`
     – Add `GetBoundChannelsForModels([]string)` to retrieve channels for multiple models in a single SQL (`IN (?)`) and deduplicate with `GROUP BY`.

   • `controller/model_meta.go`
     – In non-exact `fillModelExtra`:
       – Remove per-model `GetBoundChannels` calls.
       – Collect matched model names, then call `GetBoundChannelsForModels` once and merge results into `channelSet`.
       – Minor cleanup on loop logic; channel aggregation now happens after quota/group/endpoint processing.

Impact
------
• Eliminates N+1 query pattern for prefix/suffix/contains rules.
• Reduces DB round-trips from *N + 1* to **1**, markedly speeding up the model-management list load.
• Keeps existing `GetBoundChannels` API intact for single-model scenarios; no breaking changes.
t0ng7u 6 bulan lalu
induk
melakukan
195be56c46
2 mengubah file dengan 33 tambahan dan 17 penghapusan
  1. 18 17
      controller/model_meta.go
  2. 15 0
      model/model_meta.go

+ 18 - 17
controller/model_meta.go

@@ -188,6 +188,7 @@ func fillModelExtra(m *model.Model) {
 
 	// 端点去重集合
 	endpointSet := make(map[constant.EndpointType]struct{})
+
 	// 已绑定渠道去重集合
 	channelSet := make(map[string]model.BoundChannel)
 	// 分组去重集合
@@ -224,14 +225,6 @@ func fillModelExtra(m *model.Model) {
 
 		// 收集计费类型
 		quotaTypeSet[p.QuotaType] = struct{}{}
-
-		// 收集渠道
-		if channels, err := model.GetBoundChannels(p.ModelName); err == nil {
-			for _, ch := range channels {
-				key := ch.Name + "_" + strconv.Itoa(ch.Type)
-				channelSet[key] = ch
-			}
-		}
 	}
 
 	// 序列化端点
@@ -245,15 +238,6 @@ func fillModelExtra(m *model.Model) {
 		}
 	}
 
-	// 序列化渠道
-	if len(channelSet) > 0 {
-		channels := make([]model.BoundChannel, 0, len(channelSet))
-		for _, ch := range channelSet {
-			channels = append(channels, ch)
-		}
-		m.BoundChannels = channels
-	}
-
 	// 序列化分组
 	if len(groupSet) > 0 {
 		groups := make([]string, 0, len(groupSet))
@@ -272,6 +256,23 @@ func fillModelExtra(m *model.Model) {
 		m.QuotaType = -1
 	}
 
+	// 批量查询并序列化渠道
+	if len(matchedNames) > 0 {
+		if channels, err := model.GetBoundChannelsForModels(matchedNames); err == nil {
+			for _, ch := range channels {
+				key := ch.Name + "_" + strconv.Itoa(ch.Type)
+				channelSet[key] = ch
+			}
+		}
+		if len(channelSet) > 0 {
+			chs := make([]model.BoundChannel, 0, len(channelSet))
+			for _, ch := range channelSet {
+				chs = append(chs, ch)
+			}
+			m.BoundChannels = chs
+		}
+	}
+
 	// 设置匹配信息
 	m.MatchedModels = matchedNames
 	m.MatchedCount = len(matchedNames)

+ 15 - 0
model/model_meta.go

@@ -139,6 +139,21 @@ func GetBoundChannels(modelName string) ([]BoundChannel, error) {
 	return channels, err
 }
 
+// GetBoundChannelsForModels 批量查询多模型的绑定渠道并去重返回
+func GetBoundChannelsForModels(modelNames []string) ([]BoundChannel, error) {
+    if len(modelNames) == 0 {
+        return make([]BoundChannel, 0), nil
+    }
+    var channels []BoundChannel
+    err := DB.Table("channels").
+        Select("channels.name, channels.type").
+        Joins("join abilities on abilities.channel_id = channels.id").
+        Where("abilities.model IN ? AND abilities.enabled = ?", modelNames, true).
+        Group("channels.id").
+        Scan(&channels).Error
+    return channels, err
+}
+
 // FindModelByNameWithRule 根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含
 func FindModelByNameWithRule(name string) (*Model, error) {
 	// 1. 精确匹配