From eab21e5d8d1a8bf8e3198d8324552c0f12e36a66 Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Mon, 24 Nov 2025 14:02:01 +0200 Subject: [PATCH] Cache sorted resource IDs as slice --- engine/resources.go | 25 +++----- engine/z_resources_test.go | 116 +++++++------------------------------ 2 files changed, 29 insertions(+), 112 deletions(-) diff --git a/engine/resources.go b/engine/resources.go index f7bd08952..00cc92d8b 100644 --- a/engine/resources.go +++ b/engine/resources.go @@ -367,15 +367,6 @@ func (rs Resources) unlock() { } } -// resIDsMp returns a map of resource IDs which is used for caching -func (rs Resources) resIDsMp() (mp utils.StringSet) { - mp = make(utils.StringSet) - for _, r := range rs { - mp.Add(r.ID) - } - return mp -} - // recordUsage will record the usage in all the resource limits, failing back on errors func (rs Resources) recordUsage(ru *ResourceUsage) (err error) { var nonReservedIdx int // index of first resource not reserved @@ -640,16 +631,16 @@ func (rS *ResourceService) processThresholds(rs Resources, opts map[string]any) // matchingResourcesForEvent returns ordered list of matching resources which are active by the time of the call func (rS *ResourceService) matchingResourcesForEvent(tnt string, ev *utils.CGREvent, evUUID string, usageTTL *time.Duration) (rs Resources, err error) { - var rIDs utils.StringSet evNm := utils.MapStorage{ utils.MetaReq: ev.Event, utils.MetaOpts: ev.APIOpts, } + var itemIDs []string if x, ok := Cache.Get(utils.CacheEventResources, evUUID); ok { // The ResourceIDs were cached as utils.StringSet{"resID":bool} if x == nil { return nil, utils.ErrNotFound } - rIDs = x.(utils.StringSet) + itemIDs = x.([]string) defer func() { // make sure we uncache if we find errors if err != nil { if errCh := Cache.Remove(utils.CacheEventResources, evUUID, @@ -660,7 +651,7 @@ func (rS *ResourceService) matchingResourcesForEvent(tnt string, ev *utils.CGREv }() } else { // select the resourceIDs out of dataDB - rIDs, err = MatchingItemIDsForEvent(evNm, + rIDs, err := MatchingItemIDsForEvent(evNm, rS.cgrcfg.ResourceSCfg().StringIndexedFields, rS.cgrcfg.ResourceSCfg().PrefixIndexedFields, rS.cgrcfg.ResourceSCfg().SuffixIndexedFields, @@ -675,13 +666,11 @@ func (rS *ResourceService) matchingResourcesForEvent(tnt string, ev *utils.CGREv return nil, errCh } } - return + return nil, err } + // Lock items in sorted order to prevent AB-BA deadlock. + itemIDs = slices.Sorted(maps.Keys(rIDs)) } - - // Lock items in sorted order to prevent AB-BA deadlock. - itemIDs := slices.Sorted(maps.Keys(rIDs)) - rs = make(Resources, 0, len(itemIDs)) for _, id := range itemIDs { lkPrflID := guardian.Guardian.GuardIDs("", @@ -749,7 +738,7 @@ func (rS *ResourceService) matchingResourcesForEvent(tnt string, ev *utils.CGREv break } } - if err = Cache.Set(utils.CacheEventResources, evUUID, rs.resIDsMp(), nil, true, ""); err != nil { + if err = Cache.Set(utils.CacheEventResources, evUUID, itemIDs, nil, true, ""); err != nil { rs.unlock() } return diff --git a/engine/z_resources_test.go b/engine/z_resources_test.go index 8830c1525..ec917a7d3 100644 --- a/engine/z_resources_test.go +++ b/engine/z_resources_test.go @@ -1856,81 +1856,6 @@ func TestResourceUsageTTLCase4(t *testing.T) { } } -func TestResourceResIDsMp(t *testing.T) { - resprf := []*ResourceProfile{ - { - Tenant: config.CgrConfig().GeneralCfg().DefaultTenant, - ID: "ResourceProfile1", - FilterIDs: []string{"FLTR_RES_1"}, - ActivationInterval: &utils.ActivationInterval{ - ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), - }, - UsageTTL: 10 * time.Second, - Limit: 10.00, - AllocationMessage: "AllocationMessage", - Weight: 20.00, - ThresholdIDs: []string{""}, - }, - { - Tenant: config.CgrConfig().GeneralCfg().DefaultTenant, - ID: "ResourceProfile2", // identifier of this resource - FilterIDs: []string{"FLTR_RES_2"}, - ActivationInterval: &utils.ActivationInterval{ - ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), - }, - UsageTTL: 10 * time.Second, - Limit: 10.00, - AllocationMessage: "AllocationMessage", - Weight: 20.00, - ThresholdIDs: []string{""}, - }, - { - Tenant: config.CgrConfig().GeneralCfg().DefaultTenant, - ID: "ResourceProfile3", - FilterIDs: []string{"FLTR_RES_3"}, - ActivationInterval: &utils.ActivationInterval{ - ActivationTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), - }, - UsageTTL: 10 * time.Second, - Limit: 10.00, - AllocationMessage: "AllocationMessage", - Weight: 20.00, - ThresholdIDs: []string{""}, - }, - } - resourceTest := Resources{ - { - Tenant: config.CgrConfig().GeneralCfg().DefaultTenant, - ID: "ResourceProfile1", - Usages: map[string]*ResourceUsage{}, - TTLIdx: []string{}, - rPrf: resprf[0], - }, - { - Tenant: config.CgrConfig().GeneralCfg().DefaultTenant, - ID: "ResourceProfile2", - Usages: map[string]*ResourceUsage{}, - TTLIdx: []string{}, - rPrf: resprf[1], - }, - { - Tenant: config.CgrConfig().GeneralCfg().DefaultTenant, - ID: "ResourceProfile3", - Usages: map[string]*ResourceUsage{}, - TTLIdx: []string{}, - rPrf: resprf[2], - }, - } - expected := utils.StringSet{ - "ResourceProfile1": struct{}{}, - "ResourceProfile2": struct{}{}, - "ResourceProfile3": struct{}{}, - } - if rcv := resourceTest.resIDsMp(); !reflect.DeepEqual(rcv, expected) { - t.Errorf("Expecting: %+v, received: %+v", expected, rcv) - } -} - func TestResourceMatchWithIndexFalse(t *testing.T) { var dmRES *DataManager cfg := config.NewDefaultCGRConfig() @@ -2205,9 +2130,11 @@ func TestResourceCaching(t *testing.T) { t.Errorf("Expecting: nil, received: %s", err) } - res := &Resource{Tenant: resProf.Tenant, + res := &Resource{ + Tenant: resProf.Tenant, ID: resProf.ID, - Usages: make(map[string]*ResourceUsage)} + Usages: make(map[string]*ResourceUsage), + } if err := Cache.Set(utils.CacheResources, "cgrates.org:ResourceProfileCached", res, nil, cacheCommit(utils.EmptyString), utils.EmptyString); err != nil { @@ -2215,7 +2142,7 @@ func TestResourceCaching(t *testing.T) { } resources := Resources{res} - if err := Cache.Set(utils.CacheEventResources, "TestResourceCaching", resources.resIDsMp(), nil, true, ""); err != nil { + if err := Cache.Set(utils.CacheEventResources, "TestResourceCaching", []string{resProf.ID}, nil, true, ""); err != nil { t.Errorf("Expecting: nil, received: %s", err) } @@ -2224,7 +2151,8 @@ func TestResourceCaching(t *testing.T) { ID: utils.UUIDSha1Prefix(), Event: map[string]any{ "Account": "1001", - "Destination": "3002"}, + "Destination": "3002", + }, } mres, err := rS.matchingResourcesForEvent(ev.Tenant, ev, @@ -5653,7 +5581,7 @@ func TestResourceMatchingResourcesForEventNotFoundInDB(t *testing.T) { rS := NewResourceService(dmRES, cfg, &FilterS{dm: dmRES, cfg: cfg}, nil) - Cache.Set(utils.CacheEventResources, "TestResourceMatchingResourcesForEventNotFoundInDB", utils.StringSet{"Res2": {}}, nil, true, utils.NonTransactional) + Cache.Set(utils.CacheEventResources, "TestResourceMatchingResourcesForEventNotFoundInDB", []string{"Res2"}, nil, true, utils.NonTransactional) _, err := rS.matchingResourcesForEvent("cgrates.org", new(utils.CGREvent), "TestResourceMatchingResourcesForEventNotFoundInDB", utils.DurationPointer(10*time.Second)) if err != utils.ErrNotFound { @@ -5675,8 +5603,8 @@ func TestResourceMatchingResourcesForEventLocks(t *testing.T) { &FilterS{dm: dm, cfg: cfg}, nil) Cache.Clear(nil) prfs := make([]*ResourceProfile, 0) - ids := utils.StringSet{} - for i := 0; i < 10; i++ { + ids := make([]string, 0, 11) + for i := range 10 { rPrf := &ResourceProfile{ Tenant: "cgrates.org", ID: fmt.Sprintf("RES%d", i), @@ -5688,7 +5616,7 @@ func TestResourceMatchingResourcesForEventLocks(t *testing.T) { } dm.SetResourceProfile(rPrf, true) prfs = append(prfs, rPrf) - ids.Add(rPrf.ID) + ids = append(ids, rPrf.ID) } dm.RemoveResource("cgrates.org", "RES1") Cache.Set(utils.CacheEventResources, "TestResourceMatchingResourcesForEventLocks", ids, nil, true, utils.NonTransactional) @@ -5727,8 +5655,8 @@ func TestResourceMatchingResourcesForEventLocks2(t *testing.T) { &FilterS{dm: dm, cfg: cfg}, nil) Cache.Clear(nil) prfs := make([]*ResourceProfile, 0) - ids := utils.StringSet{} - for i := 0; i < 10; i++ { + ids := make([]string, 0, 11) + for i := range 10 { rPrf := &ResourceProfile{ Tenant: "cgrates.org", ID: fmt.Sprintf("RES%d", i), @@ -5740,7 +5668,7 @@ func TestResourceMatchingResourcesForEventLocks2(t *testing.T) { } dm.SetResourceProfile(rPrf, true) prfs = append(prfs, rPrf) - ids.Add(rPrf.ID) + ids = append(ids, rPrf.ID) } rPrf := &ResourceProfile{ Tenant: "cgrates.org", @@ -5757,7 +5685,7 @@ func TestResourceMatchingResourcesForEventLocks2(t *testing.T) { t.Fatal(err) } prfs = append(prfs, rPrf) - ids.Add(rPrf.ID) + ids = append(ids, rPrf.ID) Cache.Set(utils.CacheEventResources, "TestResourceMatchingResourcesForEventLocks2", ids, nil, true, utils.NonTransactional) _, err := rS.matchingResourcesForEvent("cgrates.org", new(utils.CGREvent), "TestResourceMatchingResourcesForEventLocks2", utils.DurationPointer(10*time.Second)) @@ -5793,8 +5721,8 @@ func TestResourceMatchingResourcesForEventLocksActivationInterval(t *testing.T) rS := NewResourceService(dm, cfg, &FilterS{dm: dm, cfg: cfg}, nil) - ids := utils.StringSet{} - for i := 0; i < 10; i++ { + ids := make([]string, 0, 10) + for i := range 10 { rPrf := &ResourceProfile{ Tenant: "cgrates.org", ID: fmt.Sprintf("RES%d", i), @@ -5805,7 +5733,7 @@ func TestResourceMatchingResourcesForEventLocksActivationInterval(t *testing.T) ThresholdIDs: []string{utils.MetaNone}, } dm.SetResourceProfile(rPrf, true) - ids.Add(rPrf.ID) + ids = append(ids, rPrf.ID) } rPrf := &ResourceProfile{ Tenant: "cgrates.org", @@ -5820,7 +5748,7 @@ func TestResourceMatchingResourcesForEventLocksActivationInterval(t *testing.T) }, } dm.SetResourceProfile(rPrf, true) - ids.Add(rPrf.ID) + ids = append(ids, rPrf.ID) Cache.Set(utils.CacheEventResources, "TestResourceMatchingResourcesForEventLocks2", ids, nil, true, utils.NonTransactional) mres, err := rS.matchingResourcesForEvent("cgrates.org", &utils.CGREvent{Time: utils.TimePointer(time.Now())}, "TestResourceMatchingResourcesForEventLocks2", utils.DurationPointer(10*time.Second)) @@ -5872,9 +5800,9 @@ func TestResourceMatchingResourcesForEventLocks3(t *testing.T) { rS := NewResourceService(dm, cfg, &FilterS{dm: dm, cfg: cfg}, nil) - ids := utils.StringSet{} - for i := 0; i < 10; i++ { - ids.Add(fmt.Sprintf("RES%d", i)) + ids := make([]string, 0, 11) + for i := range 10 { + ids = append(ids, fmt.Sprintf("RES%d", i)) } Cache.Set(utils.CacheEventResources, "TestResourceMatchingResourcesForEventLocks3", ids, nil, true, utils.NonTransactional) _, err := rS.matchingResourcesForEvent("cgrates.org", new(utils.CGREvent),