@@ -23,6 +23,7 @@ import (
2323 "github.com/prometheus/client_golang/prometheus/promhttp"
2424 "github.com/redis/rueidis"
2525 "github.com/sirupsen/logrus"
26+ "golang.org/x/time/rate"
2627)
2728
2829// getTaskTimeout 環境変数からタスクタイムアウトを取得(デフォルト600秒)
@@ -167,6 +168,10 @@ func runSubscriber(app *container.SubscriberApp, cleanup *container.Cleanup, log
167168 }
168169 }()
169170
171+ // Gemini API レートリミッター(3000 RPM = 50 RPS、バースト50)
172+ // チャンク分割・embedding生成の両方に適用してレートリミット超過を防ぐ
173+ geminiRateLimiter := rate .NewLimiter (rate .Every (time .Minute / 3000 ), 50 )
174+
170175 logger .WithField ("max_concurrent_jobs" , app .SubscriberConfig .MaxConcurrentJobs ).Info ("Subscriber is listening for messages..." )
171176
172177 // Create context for subscription that can be cancelled
@@ -239,7 +244,7 @@ func runSubscriber(app *container.SubscriberApp, cleanup *container.Cleanup, log
239244 }()
240245
241246 start := time .Now ()
242- err := processMessage (subCtx , app .DB , app .Redis , app .LLMFactory , app .LockService , msg .Message , logger )
247+ err := processMessage (subCtx , app .DB , app .Redis , app .LLMFactory , app .LockService , geminiRateLimiter , msg .Message , logger )
243248 duration := time .Since (start )
244249
245250 // メトリクス更新は processMessage 内で行う
@@ -336,7 +341,7 @@ func runSubscriber(app *container.SubscriberApp, cleanup *container.Cleanup, log
336341 return nil
337342}
338343
339- func processMessage (ctx context.Context , db * sql.DB , redisClient rueidis.Client , llmFactory container.LLMClientFactory , lockService container.LockService , payload string , logger * logrus.Entry ) error {
344+ func processMessage (ctx context.Context , db * sql.DB , redisClient rueidis.Client , llmFactory container.LLMClientFactory , lockService container.LockService , geminiRateLimiter * rate. Limiter , payload string , logger * logrus.Entry ) error {
340345 start := time .Now ()
341346
342347 // まずメッセージタイプを確認
@@ -413,7 +418,7 @@ func processMessage(ctx context.Context, db *sql.DB, redisClient rueidis.Client,
413418 messagesProcessedCounter .WithLabelValues ("diary_embedding" , "error" ).Inc ()
414419 return fmt .Errorf ("failed to unmarshal diary embedding message: %w" , unmarshalErr )
415420 }
416- err = generateDiaryEmbedding (ctx , db , llmFactory , message .UserID , message .DiaryID , logger )
421+ err = generateDiaryEmbedding (ctx , db , llmFactory , geminiRateLimiter , message .UserID , message .DiaryID , logger )
417422 if err != nil {
418423 messagesProcessedCounter .WithLabelValues ("diary_embedding" , "error" ).Inc ()
419424 } else {
@@ -1091,7 +1096,7 @@ func generateDiaryHighlightWithLLM(ctx context.Context, db *sql.DB, llmFactory c
10911096 return highlights , nil
10921097}
10931098
1094- func generateDiaryEmbedding (ctx context.Context , db * sql.DB , llmFactory container.LLMClientFactory , userID , diaryID string , logger * logrus.Entry ) error {
1099+ func generateDiaryEmbedding (ctx context.Context , db * sql.DB , llmFactory container.LLMClientFactory , geminiRateLimiter * rate. Limiter , userID , diaryID string , logger * logrus.Entry ) error {
10951100 logger .WithFields (logrus.Fields {
10961101 "user_id" : userID ,
10971102 "diary_id" : diaryID ,
@@ -1140,6 +1145,9 @@ func generateDiaryEmbedding(ctx context.Context, db *sql.DB, llmFactory containe
11401145 }()
11411146
11421147 // 4. 日記を話題ごとのチャンクに分割する(失敗時は日記全体を1チャンクとしてフォールバック)
1148+ if err := geminiRateLimiter .Wait (ctx ); err != nil {
1149+ return fmt .Errorf ("rate limiter cancelled before SplitDiaryIntoChunks: %w" , err )
1150+ }
11431151 chunkDataList , err := geminiClient .SplitDiaryIntoChunks (ctx , diaryContent )
11441152 if err != nil {
11451153 logger .WithFields (logrus.Fields {
@@ -1172,6 +1180,10 @@ func generateDiaryEmbedding(ctx context.Context, db *sql.DB, llmFactory containe
11721180 defer embWg .Done ()
11731181 // 時間的クエリの精度向上のため日付情報を先頭に付与する
11741182 enrichedChunk := fmt .Sprintf ("%d年%d月%d日の日記:\n %s" , diaryDate .Year (), int (diaryDate .Month ()), diaryDate .Day (), cd .Content )
1183+ if waitErr := geminiRateLimiter .Wait (ctx ); waitErr != nil {
1184+ embResults [idx ] = embeddingResult {err : fmt .Errorf ("rate limiter cancelled before GenerateEmbedding for chunk %d: %w" , idx , waitErr )}
1185+ return
1186+ }
11751187 embedding , err := geminiClient .GenerateEmbedding (ctx , enrichedChunk , true )
11761188 if err != nil {
11771189 embResults [idx ] = embeddingResult {err : fmt .Errorf ("failed to generate embedding for chunk %d: %w" , idx , err )}
0 commit comments