Skip to content

Replace SQS singleton with per-pipeline client cache with TTL eviction - #291

Open
gshikhar2021 wants to merge 2 commits into
redhat-data-and-ai:mainfrom
gshikhar2021:separate-sqsClient
Open

Replace SQS singleton with per-pipeline client cache with TTL eviction#291
gshikhar2021 wants to merge 2 commits into
redhat-data-and-ai:mainfrom
gshikhar2021:separate-sqsClient

Conversation

@gshikhar2021

Copy link
Copy Markdown
Contributor

No description provided.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the SQS client management strategy to improve scalability and resource management. By moving from a single global client to a per-pipeline cache, the system can now effectively handle multiple concurrent pipelines. The addition of TTL-based eviction ensures that inactive clients are cleaned up, preventing memory leaks and stale connections.

Highlights

  • SQS Client Caching: Replaced the global SQS singleton with a thread-safe per-pipeline client cache to support multiple pipelines simultaneously.
  • TTL Eviction: Implemented a background cleanup process that evicts stale SQS clients from the cache based on a configurable time-to-live (TTL).
  • Controller Lifecycle Integration: Updated the controller to handle SQS client cleanup when a SourceCrawler resource is deleted and integrated cache initialization into the main application startup.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the single global SQS client with a multi-tenant, TTL-evicted client cache (SQSClientCache) to support multiple pipelines. The review feedback highlights critical issues that need to be addressed: using only the resource name as a cache key can lead to cross-namespace cache collisions, so namespaced names should be used instead. Additionally, the cache currently lacks support for credential rotation because it does not compare incoming AWS configurations with cached ones, and the cleanup routine is susceptible to a panic if initialized with an invalid TTL.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +81 to +84
if apierrors.IsNotFound(err) {
awsclienthandler.DeleteSQSClient(req.Name)
return ctrl.Result{}, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Using only req.Name as the cache key can lead to cache collisions if multiple SourceCrawler resources with the same name exist in different namespaces. Since this operator can watch multiple namespaces, we should use the namespaced name (e.g., req.NamespacedName.String()) as the cache key to prevent cross-namespace client leakage.

Suggested change
if apierrors.IsNotFound(err) {
awsclienthandler.DeleteSQSClient(req.Name)
return ctrl.Result{}, nil
}
if apierrors.IsNotFound(err) {
awsclienthandler.DeleteSQSClient(req.NamespacedName.String())
return ctrl.Result{}, nil
}

Comment on lines +169 to +172
if _, err := awsclienthandler.NewSQSClientFromConfig(ctx, sourceAWSConfig, sourceCrawlerCR.Name); err != nil {
return ctrl.Result{}, r.handleError(ctx, sourceCrawlerCR, fmt.Errorf("failed to create SQS client: %w", err))
}
return handleSQSWakeUp(ctx, sqsQueueURL, sourceCrawlerConfig.S3Config.Bucket, sourceCrawlerConfig.S3Config.Prefix), nil
return handleSQSWakeUp(ctx, sqsQueueURL, sourceCrawlerConfig.S3Config.Bucket, sourceCrawlerConfig.S3Config.Prefix, sourceCrawlerCR.Name), nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Using sourceCrawlerCR.Name as the cache key can cause cache collisions across different namespaces. We should use the namespaced name string of the resource to ensure uniqueness.

			namespacedName := types.NamespacedName{Namespace: sourceCrawlerCR.Namespace, Name: sourceCrawlerCR.Name}.String()
			if _, err := awsclienthandler.NewSQSClientFromConfig(ctx, sourceAWSConfig, namespacedName); err != nil {
				return ctrl.Result{}, r.handleError(ctx, sourceCrawlerCR, fmt.Errorf("failed to create SQS client: %w", err))
			}
			return handleSQSWakeUp(ctx, sqsQueueURL, sourceCrawlerConfig.S3Config.Bucket, sourceCrawlerConfig.S3Config.Prefix, namespacedName), nil

Comment on lines +36 to +39
type sqsEntry struct {
client *sqs.Client
lastUsed time.Time
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To support credential rotation and updates, we should store the AWSConfig in the sqsEntry so that we can detect when the configuration has changed and recreate the client accordingly.

Suggested change
type sqsEntry struct {
client *sqs.Client
lastUsed time.Time
}
type sqsEntry struct {
client *sqs.Client
awsConfig AWSConfig
lastUsed time.Time
}

Comment thread pkg/awsclienthandler/sqs_client.go Outdated
Comment on lines 98 to 102
entry, ok := sqsClientCache.GetClient(pipelineName)
if ok {
logger.Info("Using existing SQS client for the pipeline", "pipelineName", pipelineName)
return entry.client, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the AWS credentials are rotated or updated in the Secret, the controller will continue using the cached client with the old/stale credentials because it ignores the incoming awsConfig when a cached client exists. We should compare the incoming configuration with the cached one and recreate the client if they differ.

Suggested change
entry, ok := sqsClientCache.GetClient(pipelineName)
if ok {
logger.Info("Using existing SQS client for the pipeline", "pipelineName", pipelineName)
return entry.client, nil
}
entry, ok := sqsClientCache.GetClient(pipelineName)
if ok && awsConfig != nil && entry.awsConfig == *awsConfig {
logger.Info("Using existing SQS client for the pipeline", "pipelineName", pipelineName)
return entry.client, nil
}

Comment thread pkg/awsclienthandler/sqs_client.go Outdated
Comment on lines +119 to +120
newEntry := &sqsEntry{client: sqsClient, lastUsed: time.Now()}
sqsClientCache.SetClient(pipelineName, newEntry)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Store the AWSConfig in the new cache entry to enable configuration comparison on subsequent calls.

Suggested change
newEntry := &sqsEntry{client: sqsClient, lastUsed: time.Now()}
sqsClientCache.SetClient(pipelineName, newEntry)
var cfgVal AWSConfig
if awsConfig != nil {
cfgVal = *awsConfig
}
newEntry := &sqsEntry{
client: sqsClient,
awsConfig: cfgVal,
lastUsed: time.Now(),
}
sqsClientCache.SetClient(pipelineName, newEntry)

Comment on lines +68 to +69
func (c *SQSClientCache) startCleanup(ctx context.Context, ttl time.Duration) {
ticker := time.NewTicker(ttl / 2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If ttl is less than or equal to 0, time.NewTicker(ttl / 2) will panic. We should add a defensive check to prevent panics if an invalid TTL is passed.

func (c *SQSClientCache) startCleanup(ctx context.Context, ttl time.Duration) {
	if ttl <= 0 {
		return
	}
	ticker := time.NewTicker(ttl / 2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant