Sobes.tech
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:

  1. Potential loop variable leak: In the anonymous function, hosts[i] is used, but i may change by the time the goroutine executes. You should pass hosts[i] as a parameter to the function.

  2. Launching too many goroutines simultaneously: If hosts contains thousands of elements, as many goroutines will be created, leading to high memory and CPU usage.

  3. 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 host value 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.