perf(queue): 限制大媒体任务内存并消除准入惊群

将同步与异步非文本任务的准入唤醒改为按任务和全局队首推进,批量续租等待者,避免大量请求同时争抢 advisory lock 和数据库连接。

对 Base64 请求解析、素材解码、上游媒体执行和结果物化增加分层并发限制,复用请求素材并释放重复 Gemini wire 数据;生产 API 默认入口解析并发 16,媒体物化并发 8。

新增 1000 个同步 Gemini 图像编辑请求的模拟上游压力验收,10 MiB 输入和输出下全部成功,Heap 峰值增长约 2.58 GiB,并验证共享上传哈希、单次 attempt 与本地零落盘。

验证:go test ./... -count=1;go vet ./...;gofmt -l 无输出;kubectl kustomize deploy/kubernetes/production;10 MiB Gemini Base64 千任务压力测试通过。
This commit is contained in:
2026-07-30 13:13:32 +08:00
parent fd3b6bf042
commit ea58d21d03
26 changed files with 1980 additions and 74 deletions
@@ -16,6 +16,7 @@ import (
"time"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
func TestRequestAssetFromValueDetectsDataURLAndRawBase64(t *testing.T) {
@@ -66,6 +67,33 @@ func TestRequestAssetFromValueDetectsGeminiInlineData(t *testing.T) {
}
}
func TestRequestAssetStillUsableRequiresExistingLocalFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "asset.png")
if err := os.WriteFile(path, []byte("image"), 0o600); err != nil {
t.Fatalf("write local request asset: %v", err)
}
asset := store.RequestAsset{
URL: "http://127.0.0.1/static/uploaded/asset.png",
StorageProvider: "local_static",
LocalPath: path,
ByteSize: 5,
}
if !requestAssetStillUsable(asset, time.Now()) {
t.Fatal("existing local request asset was rejected")
}
asset.ByteSize = 6
if requestAssetStillUsable(asset, time.Now()) {
t.Fatal("truncated local request asset was treated as reusable")
}
asset.ByteSize = 5
if err := os.Remove(path); err != nil {
t.Fatalf("remove local request asset: %v", err)
}
if requestAssetStillUsable(asset, time.Now()) {
t.Fatal("missing local request asset was treated as reusable")
}
}
func TestCanonicalConversationMessageHashUsesTextAndAssetRefs(t *testing.T) {
message := map[string]any{
"role": "user",