Implement asynchronous AI recommendation cache refresh in company service
continuous-integration/drone/push Build is passing

- Introduced `refreshAIRecommendationsCacheAsync` method to refresh AI recommendations in the background, improving responsiveness by serving the previous cache until the refresh completes.
- Updated `StartAIOnboarding` to call the new asynchronous cache refresh method instead of invalidating the cache directly.
- Added logging for cache refresh operations, including success and error handling, to enhance observability.

This update enhances the AI recommendation caching mechanism, providing a smoother onboarding experience and reducing latency in recommendation retrieval.
This commit is contained in:
Mazyar
2026-06-23 13:33:33 +03:30
parent a2661651c9
commit 326e49886b
+44 -14
View File
@@ -85,7 +85,8 @@ func (s *companyService) StartAIOnboarding(ctx context.Context, companyID string
return nil, fmt.Errorf("failed to start AI onboarding: %w", err) return nil, fmt.Errorf("failed to start AI onboarding: %w", err)
} }
s.invalidateAIRecommendationCache(ctx, companyID) // Prefetch recommendations in the background; keep serving the previous cache until refresh completes.
s.refreshAIRecommendationsCacheAsync(companyID)
return &OnboardingResponse{Status: result.Status}, nil return &OnboardingResponse{Status: result.Status}, nil
} }
@@ -143,6 +144,48 @@ func (s *companyService) GetAIRecommendations(ctx context.Context, companyID str
return nil, errors.New("company not found") return nil, errors.New("company not found")
} }
return s.fetchAndCacheAIRecommendations(ctx, companyID)
}
func (s *companyService) refreshAIRecommendationsCacheAsync(companyID string) {
if s.aiRecommendationClient == nil {
return
}
companyID = strings.TrimSpace(companyID)
if companyID == "" {
return
}
go func() {
ctx := context.Background()
s.logger.Info("Refreshing AI recommendation cache in background", map[string]interface{}{
"company_id": companyID,
})
if _, err := s.repository.GetByID(ctx, companyID); err != nil {
s.logger.Error("Failed to load company for AI recommendation refresh", map[string]interface{}{
"company_id": companyID,
"error": err.Error(),
})
return
}
if _, err := s.fetchAndCacheAIRecommendations(ctx, companyID); err != nil {
s.logger.Error("Failed to refresh AI recommendation cache", map[string]interface{}{
"company_id": companyID,
"error": err.Error(),
})
return
}
s.logger.Info("AI recommendation cache refreshed", map[string]interface{}{
"company_id": companyID,
})
}()
}
func (s *companyService) fetchAndCacheAIRecommendations(ctx context.Context, companyID string) ([]RecommendedTenderResponse, error) {
s.logger.Info("Fetching AI tender recommendations", map[string]interface{}{ s.logger.Info("Fetching AI tender recommendations", map[string]interface{}{
"company_id": companyID, "company_id": companyID,
}) })
@@ -228,19 +271,6 @@ func (s *companyService) cacheAIRecommendations(ctx context.Context, companyID s
} }
} }
func (s *companyService) invalidateAIRecommendationCache(ctx context.Context, companyID string) {
if s.redisClient == nil || s.recommendationCacheTTL <= 0 {
return
}
if err := s.redisClient.Del(ctx, aiRecommendationCacheKey(companyID)); err != nil {
s.logger.Warn("Failed to invalidate AI recommendation cache", map[string]interface{}{
"company_id": companyID,
"error": err.Error(),
})
}
}
func (s *companyService) buildOnboardingDocuments(fileIDs []string) (string, error) { func (s *companyService) buildOnboardingDocuments(fileIDs []string) (string, error) {
if len(fileIDs) == 0 { if len(fileIDs) == 0 {
return "[]", nil return "[]", nil