Skip to content

Commit 81bd491

Browse files
authored
Merge pull request #674 from meshery/registry-optimizations
Registry optimizations
2 parents d192c66 + 187887d commit 81bd491

File tree

3 files changed

+96
-4
lines changed

3 files changed

+96
-4
lines changed

build/Makefile.core.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ GOVERSION = 1.23.4
2222
GOPATH = $(shell go env GOPATH)
2323
GOBIN = $(GOPATH)/bin
2424

25-
SHELL :=/bin/bash -o pipefail
25+
SHELL :=/usr/bin/env bash -o pipefail
2626

2727
#-----------------------------------------------------------------------------
2828
# Build

models/meshmodel/registry/registry.go

+86-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ package registry
22

33
import (
44
"fmt"
5-
"strings"
6-
"time"
7-
85
"github.com/gofrs/uuid"
96
"github.com/layer5io/meshkit/database"
107
models "github.com/layer5io/meshkit/models/meshmodel/core/v1beta1"
@@ -17,6 +14,9 @@ import (
1714
"golang.org/x/text/cases"
1815
"golang.org/x/text/language"
1916
"gorm.io/gorm/clause"
17+
"strings"
18+
"sync"
19+
"time"
2020
)
2121

2222
// MeshModelRegistrantData struct defines the body of the POST request that is sent to the capability
@@ -31,6 +31,43 @@ type MeshModelRegistrantData struct {
3131
EntityType entity.EntityType `json:"entityType"`
3232
Entity []byte `json:"entity"` //This will be type converted to appropriate entity on server based on passed entity type
3333
}
34+
35+
type EntityCacheValue struct {
36+
Entities []entity.Entity
37+
Count int64
38+
Unique int
39+
err error
40+
}
41+
42+
// RegistryEntityCache is a thread-safe cache for storing entity query results.
43+
//
44+
// This cache maps entity filters (`entity.Filter`) to their corresponding results (`EntityCacheValue`).
45+
// It uses `sync.Map` for safe concurrent access, making it suitable for multi-goroutine environments.
46+
//
47+
// The caller is responsible for managing the cache's lifecycle, including eviction and expiration if needed.
48+
type RegistryEntityCache struct {
49+
cache sync.Map
50+
}
51+
52+
func filterKey(f entity.Filter) string {
53+
// Convert the filter to a unique string representation (adjust as needed)
54+
return fmt.Sprintf("%v", f)
55+
}
56+
57+
// Get retrieves a cached value
58+
func (c *RegistryEntityCache) Get(f entity.Filter) (EntityCacheValue, bool) {
59+
value, exists := c.cache.Load(filterKey(f))
60+
if exists {
61+
return value.(EntityCacheValue), true
62+
}
63+
return EntityCacheValue{}, false
64+
}
65+
66+
// Set stores a value in the cache
67+
func (c *RegistryEntityCache) Set(f entity.Filter, value EntityCacheValue) {
68+
c.cache.Store(filterKey(f), value)
69+
}
70+
3471
type Registry struct {
3572
ID uuid.UUID
3673
RegistrantID uuid.UUID
@@ -196,6 +233,52 @@ func (rm *RegistryManager) GetEntities(f entity.Filter) ([]entity.Entity, int64,
196233
return f.Get(rm.db)
197234
}
198235

236+
// GetEntitiesMemoized retrieves entities based on the provided filter `f`, using a concurrent-safe cache to optimize performance.
237+
//
238+
// ## Cache Behavior:
239+
// - **Cache Hit**: If the requested entities are found in the `cache`, the function returns the cached result immediately, avoiding a redundant query.
240+
// - **Cache Miss**: If the requested entities are *not* found in the cache, the function fetches them from the registry using `rm.GetEntities(f)`,
241+
// stores the result in the `cache`, and returns the newly retrieved entities.
242+
//
243+
// ## Concurrency and Thread Safety:
244+
// - The `cache` is implemented using `sync.Map`, ensuring **safe concurrent access** across multiple goroutines.
245+
// - `sync.Map` is optimized for scenarios where **reads significantly outnumber writes**, making it well-suited for caching use cases.
246+
//
247+
// ## Ownership and Responsibility:
248+
// - **RegistryManager (`rm`)**: Owns the logic for retrieving entities from the source when a cache miss occurs.
249+
// - **Caller Ownership of Cache**: The caller is responsible for providing and managing the `cache` instance.
250+
// This function does *not* handle cache eviction, expiration, or memory constraints—those concerns must be managed externally.
251+
//
252+
// ## Parameters:
253+
// - `f entity.Filter`: The filter criteria used to retrieve entities.
254+
// - `cache *RegistryEntityCache`: A pointer to a concurrent-safe cache (`sync.Map`) that stores previously retrieved entity results.
255+
//
256+
// ## Returns:
257+
// - `[]entity.Entity`: The list of retrieved entities (either from cache or freshly fetched).
258+
// - `int64`: The total count of entities matching the filter.
259+
// - `int`: The number of unique entities found.
260+
// - `error`: An error if the retrieval operation fails.
261+
func (rm *RegistryManager) GetEntitiesMemoized(f entity.Filter, cache *RegistryEntityCache) ([]entity.Entity, int64, int, error) {
262+
263+
// Attempt to retrieve from cache
264+
if cachedEntities, exists := cache.Get(f); exists && len(cachedEntities.Entities) > 0 {
265+
return cachedEntities.Entities, cachedEntities.Count, cachedEntities.Unique, cachedEntities.err
266+
}
267+
268+
// Fetch from source if cache miss
269+
entities, count, unique, err := rm.GetEntities(f)
270+
271+
// Store result in cache
272+
cache.Set(f, EntityCacheValue{
273+
Entities: entities,
274+
Count: count,
275+
Unique: unique,
276+
err: err,
277+
})
278+
279+
return entities, count, unique, err
280+
}
281+
199282
func HostnameToPascalCase(input string) string {
200283
parts := strings.Split(input, ".")
201284
caser := cases.Title(language.English)

utils/utils.go

+9
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ import (
2020
"sort"
2121
"strconv"
2222
"strings"
23+
"time"
2324
"unicode"
2425

26+
"github.com/layer5io/meshkit/logger"
2527
"github.com/layer5io/meshkit/models/meshmodel/entity"
28+
"github.com/open-policy-agent/opa/v1/logging"
2629
log "github.com/sirupsen/logrus"
2730
"gopkg.in/yaml.v3"
2831

@@ -866,3 +869,9 @@ func ParseKubeStatusErr(err *kubeerror.StatusError) (shortDescription, longDescr
866869

867870
return
868871
}
872+
873+
func trackTime(logger logger.Handler, start time.Time, name string) {
874+
875+
elapsed := time.Since(start)
876+
logger.Debugf("%s took %s\n", name, elapsed)
877+
}

0 commit comments

Comments
 (0)