diff --git a/pkg/services/authz/rbac/service.go b/pkg/services/authz/rbac/service.go index 43a6c1e685d..ec0c696f822 100644 --- a/pkg/services/authz/rbac/service.go +++ b/pkg/services/authz/rbac/service.go @@ -8,6 +8,7 @@ import ( authzv1 "github.com/grafana/authlib/authz/proto/v1" "github.com/grafana/authlib/claims" + "golang.org/x/sync/singleflight" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/apiserver/pkg/endpoints/request" @@ -49,6 +50,9 @@ type Service struct { teamCache *localcache.CacheService basicRoleCache *localcache.CacheService folderCache *localcache.CacheService + + // Deduplication of concurrent requests + sf *singleflight.Group } func NewService(sql legacysql.LegacyDatabaseProvider, identityStore legacy.LegacyIdentityStore, logger log.Logger, tracer tracing.Tracer) *Service { @@ -63,6 +67,7 @@ func NewService(sql legacysql.LegacyDatabaseProvider, identityStore legacy.Legac teamCache: localcache.New(shortCacheTTL, shortCleanupInterval), basicRoleCache: localcache.New(longCacheTTL, longCleanupInterval), folderCache: localcache.New(shortCacheTTL, shortCleanupInterval), + sf: new(singleflight.Group), } } @@ -339,38 +344,45 @@ func (s *Service) buildFolderTree(ctx context.Context, ns claims.NamespaceInfo) return cached.(map[string]FolderNode), nil } - folders, err := s.store.GetFolders(ctx, ns) + res, err, _ := s.sf.Do(ns.Value+"_buildFolderTree", func() (interface{}, error) { + folders, err := s.store.GetFolders(ctx, ns) + if err != nil { + return nil, fmt.Errorf("could not get folders: %w", err) + } + + folderMap := make(map[string]FolderNode, len(folders)) + for _, folder := range folders { + if node, has := folderMap[folder.UID]; !has { + folderMap[folder.UID] = FolderNode{ + uid: folder.UID, + parentUID: folder.ParentUID, + } + } else { + node.parentUID = folder.ParentUID + folderMap[folder.UID] = node + } + // Register that the parent has this child node + if folder.ParentUID == nil { + continue + } + if parent, has := folderMap[*folder.ParentUID]; has { + parent.childrenUIDs = append(parent.childrenUIDs, folder.UID) + folderMap[*folder.ParentUID] = parent + } else { + folderMap[*folder.ParentUID] = FolderNode{ + uid: *folder.ParentUID, + childrenUIDs: []string{folder.UID}, + } + } + } + + s.folderCache.Set(key, folderMap, 0) + return folderMap, nil + }) + if err != nil { - return nil, fmt.Errorf("could not get folders: %w", err) + return nil, err } - folderMap := make(map[string]FolderNode, len(folders)) - for _, folder := range folders { - if node, has := folderMap[folder.UID]; !has { - folderMap[folder.UID] = FolderNode{ - uid: folder.UID, - parentUID: folder.ParentUID, - } - } else { - node.parentUID = folder.ParentUID - folderMap[folder.UID] = node - } - // Register that the parent has this child node - if folder.ParentUID == nil { - continue - } - if parent, has := folderMap[*folder.ParentUID]; has { - parent.childrenUIDs = append(parent.childrenUIDs, folder.UID) - folderMap[*folder.ParentUID] = parent - } else { - folderMap[*folder.ParentUID] = FolderNode{ - uid: *folder.ParentUID, - childrenUIDs: []string{folder.UID}, - } - } - } - - s.folderCache.Set(key, folderMap, 0) - - return folderMap, nil + return res.(map[string]FolderNode), nil } diff --git a/pkg/services/authz/rbac/service_test.go b/pkg/services/authz/rbac/service_test.go index b16160419b6..e1b1487fac4 100644 --- a/pkg/services/authz/rbac/service_test.go +++ b/pkg/services/authz/rbac/service_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sync/singleflight" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" @@ -438,6 +439,7 @@ func TestService_buildFolderTree(t *testing.T) { store: store, folderCache: cacheService, logger: log.New("test"), + sf: new(singleflight.Group), } tree, err := s.buildFolderTree(ctx, ns)