Sobes.tech
Middle+

// When updating an order, we need to send order data to multiple services (third-party APIs) // the number of services is growing (may be thousands+) // we wrote code, initially everything was fine, but over time our service started consuming a lot of memory 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 an issue with using the loop variable inside a goroutine — the variable hosts[i] may change by the time the goroutine executes, leading to incorrect calls.

Additionally, with a large number of services (thousands or more), launching thousands of goroutines simultaneously will result in high memory and resource consumption.

Recommendations for fixing:

  1. Pass the variable into the closure to avoid capturing the changing variable:
for i := 0; i < len(hosts); i++ {
    host := hosts[i]
    go func() {
        response, err := s.httpClient.Send(ctx, host, order)
        if err != nil {
            s.logger.Error(ctx, "failed to send", err)
            return
        }
        s.logger.Info(ctx, "success", response)
    }()
}
  1. Limit the number of concurrently running goroutines using a semaphore or worker pool, to prevent creating thousands of goroutines at once:
sem := make(chan struct{}, 100) // limit to 100 parallel calls
for _, host := range hosts {
    sem <- struct{}{}
    go func(h string) {
        defer func() { <-sem }()
        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)
}
// wait for all goroutines to finish
for i := 0; i < cap(sem); i++ {
    sem <- struct{}{}
}

This way, we avoid memory leaks and control system load.