Skip to content

⚡ Bolt: Run datasource health checks concurrently in Registry.HealthReport - #131

Open
blue4209211 wants to merge 4 commits into
mainfrom
bolt-parallel-datasource-health-checks
Open

⚡ Bolt: Run datasource health checks concurrently in Registry.HealthReport#131
blue4209211 wants to merge 4 commits into
mainfrom
bolt-parallel-datasource-health-checks

Conversation

@blue4209211

@blue4209211 blue4209211 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Description

Optimized Registry.HealthReport in pkg/proxy/registry.go to run health checks across all registered datasources concurrently using goroutines, a sync.WaitGroup, and a sync.Mutex for map protection.

Previously, HealthReport executed health checks sequentially for every registered datasource with a 10-second timeout per check. When multiple datasources were configured and any failed or experienced high latency, health reporting blocked sequentially for O(N * latency).

  • Latency Reduction: Total health report duration reduced from O(N * latency) down to O(max(latency)).
  • Responsiveness: Ensures periodic health reports finish within a single timeout window (~10s max) even when multiple datasources fail or hang.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Enhancement (non-breaking change which improves existing functionality)
  • Refactor (non-breaking change which improves code structure)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation
  • CI/CD

How Has This Been Tested?

  • Unit tests
  • Manual testing

Checklist

  • CLA signed (the CLA bot will prompt on your first PR)
  • make validate passes (fmt + lint + test)
  • Docs updated if the wire shape, config surface, or proxy module behavior changed

@blue4209211

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 refactors the HealthReport function in pkg/proxy/registry.go to perform health checks concurrently using goroutines, preventing slow or timing-out datasources from blocking the entire process. It also adds a unit test to verify this concurrent behavior. The review feedback suggests using defer cancel() inside the goroutine to prevent context leaks and checking the parent context status before initiating health checks.

Comment thread pkg/proxy/registry.go Outdated
Comment on lines +192 to +194
checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
err := t.proxy.HealthCheck(checkCtx)
cancel()

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

Using defer cancel() is the idiomatic Go pattern to ensure that context resources are cleaned up as soon as the goroutine exits, preventing potential context leaks if the function is later refactored to include early returns.

Additionally, consider checking ctx.Err() at the beginning of the goroutine to avoid initiating health checks if the parent context has already been cancelled.

Suggested change
checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
err := t.proxy.HealthCheck(checkCtx)
cancel()
checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
err := t.proxy.HealthCheck(checkCtx)

@blue4209211

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 updates the HealthReport method in pkg/proxy/registry.go to run health checks concurrently using goroutines, preventing slow or timing-out datasources from blocking the overall reporting process. It also adds a unit test to verify this behavior and updates the documentation. The feedback suggests using defer cancel() instead of manually calling cancel() to ensure context resources are reliably released on all execution paths.

Comment thread pkg/proxy/registry.go
Comment on lines +192 to +194
Name: t.cfg.Name,
LastCheck: now,
}

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

Using defer cancel() is more idiomatic and robust than calling cancel() manually at the end of the block. It ensures that the context resources are released on all execution paths, including any future refactoring that might introduce early returns or panics, and maintains consistency with CollectAllMetadata.

Suggested change
Name: t.cfg.Name,
LastCheck: now,
}
checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
err := t.proxy.HealthCheck(checkCtx)

@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 refactors the HealthReport method in pkg/proxy/registry.go to run health checks concurrently using goroutines, preventing a single slow or timing-out datasource from blocking others. It also adds a unit test to verify this concurrent behavior. The feedback points out that returning early when ctx.Err() != nil inside the goroutine can cause datasources to be silently omitted from the final report map if the context is cancelled before the goroutine executes. Removing this early return ensures all datasources are consistently represented in the report with an explicit error status.

Comment thread pkg/proxy/registry.go Outdated
Comment on lines +185 to +187
if ctx.Err() != nil {
return
}

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

By returning early when ctx.Err() != nil, some datasources will be silently omitted from the returned report map if the context is cancelled or times out before the goroutine executes. Since t.proxy.HealthCheck(checkCtx) already respects context cancellation and will return immediately with the context error, removing this early return ensures that every datasource is consistently represented in the report with an explicit error status (e.g., context canceled) instead of being missing from the map.

@blue4209211

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 refactors the HealthReport method in pkg/proxy/registry.go to run health checks concurrently using goroutines, preventing a single slow or timing-out datasource from blocking others. It also adds a corresponding unit test to verify this behavior. The reviewer suggested limiting the maximum concurrency of these health checks using a semaphore (buffered channel) to prevent potential resource exhaustion when dealing with a large number of datasources.

Comment thread pkg/proxy/registry.go
Comment on lines +175 to +183
var mu sync.Mutex
var wg sync.WaitGroup

if !pOk || !cOk {
continue
}
// Run health checks concurrently to prevent a single slow or timing-out
// datasource from blocking the health status of other datasources.
for _, t := range targets {
wg.Add(1)
go func(t target) {
defer wg.Done()

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

Running health checks concurrently across all registered datasources without any limit can lead to resource exhaustion (such as exceeding the open file descriptor limit due to concurrent network connections, or overwhelming downstream services) if there are many datasources. Introducing a simple semaphore using a buffered channel limits the maximum concurrency while still allowing parallel execution.

	var mu sync.Mutex
	var wg sync.WaitGroup
	sem := make(chan struct{}, 10) // Limit concurrent health checks

	// Run health checks concurrently to prevent a single slow or timing-out
	// datasource from blocking the health status of other datasources.
	for _, t := range targets {
		wg.Add(1)
		go func(t target) {
			defer wg.Done()
			sem <- struct{}{}
			defer func() { <-sem }()

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