From e92a976de275af7a157d3da43b56db9f31a33a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Thu, 13 Nov 2025 12:08:14 +0100 Subject: [PATCH] fix(unified-storage): process list items concurrently (#113801) --- pkg/storage/unified/apistore/store.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/storage/unified/apistore/store.go b/pkg/storage/unified/apistore/store.go index 2ad474dc90f..5adf5e8b958 100644 --- a/pkg/storage/unified/apistore/store.go +++ b/pkg/storage/unified/apistore/store.go @@ -33,6 +33,7 @@ import ( "k8s.io/client-go/tools/cache" authtypes "github.com/grafana/authlib/types" + "github.com/grafana/dskit/concurrency" "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana/pkg/apimachinery/utils" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" @@ -472,16 +473,36 @@ func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOpti } if v.IsNil() { - v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + v.Set(reflect.MakeSlice(v.Type(), 0, len(rsp.Items))) } - for _, item := range rsp.Items { + // Pre-allocate results slice to preserve order and avoid race conditions. + // Each goroutine writes to its own index, no mutex needed. + type resultSlot struct { + obj runtime.Object + shouldAppend bool + } + results := make([]resultSlot, len(rsp.Items)) + + // Concurrently process items as some may be large and take a while to process. + err = concurrency.ForEachJob(ctx, len(rsp.Items), 10, func(ctx context.Context, idx int) error { + item := rsp.Items[idx] obj, shouldAppend, err := s.processItem(ctx, item, opts, predicate) if err != nil { return err } if shouldAppend { - v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem())) + results[idx] = resultSlot{obj: obj, shouldAppend: true} + } + return nil + }) + if err != nil { + return err + } + + for _, r := range results { + if r.shouldAppend { + v.Set(reflect.Append(v, reflect.ValueOf(r.obj).Elem())) } }