Principal
// When updating an order, we need to send order data to several services (third-party) // the number of services is growing (could be thousands+) // we wrote the code, initially everything was fine, but over time our service started consuming a lot of resources // We need to optimize this code to work efficiently with a large number of services func (s *orderService) SendOrder(ctx context.Context, hosts []string, order Order) { for i := 0; i < len(hosts); i++ { go func() { // Imagine this is a long network call response, err := s.httpClient.Send(ctx, hosts[i], order) if err != nil { s.logger.Error(ctx, "failed to send", err) return } s.logger.Info(ctx, "success", response) }() } }
sobes.tech AI
Answer from AI
The provided code has several issues that lead to high resource consumption when there are many services:
-
Potential loop variable leak: In the anonymous function,
hosts[i]is used, butimay change by the time the goroutine executes. You should passhosts[i]as a parameter to the function. -
Launching too many goroutines simultaneously: If
hostscontains thousands of elements, as many goroutines will be created, leading to high memory and CPU usage. -
Lack of concurrency control and wait for completion.
How to optimize:
- Use a goroutine pool with a limit on the number of concurrently executing tasks (e.g., via semaphore or channel).
- Pass the
hostvalue explicitly to the goroutine to avoid capturing the loop variable. - Add a mechanism to wait for all sends to complete (e.g.,
sync.WaitGroup).
Example of optimized code:
func (s *orderService) SendOrder(ctx context.Context, hosts []string, order Order) {
const maxConcurrency = 100 // limit on parallel requests
sem := make(chan struct{}, maxConcurrency)
var wg sync.WaitGroup
for _, host := range hosts {
wg.Add(1)
sem <- struct{}{} // acquire slot
go func(h string) {
defer wg.Done()
defer func() { <-sem }() // release slot
response, err := s.httpClient.Send(ctx, h, order)
if err != nil {
s.logger.Error(ctx, "failed to send", err)
return
}
s.logger.Info(ctx, "success", response)
}(host)
}
wg.Wait() // wait for all goroutines to finish
}
This approach allows controlling system load, avoiding creating too many goroutines at once, and prevents loop variable leaks.