Skip to content

add support for caching (DCNE-680)#148

Open
juchowan wants to merge 6 commits intociscoecosystem:masterfrom
juchowan:cache
Open

add support for caching (DCNE-680)#148
juchowan wants to merge 6 commits intociscoecosystem:masterfrom
juchowan:cache

Conversation

@juchowan
Copy link
Copy Markdown

No description provided.

client/client.go Outdated
}

// deepCloneContainer creates a true deep copy of a Container to prevent shared data races
func (c *Client) deepCloneContainer(original *container.Container) (*container.Container, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why expose this in client and not in gabs.go as a method/function on the container itself?

client/client.go Outdated
}

// GetSchemaWithCache retrieves schema with caching support
func (c *Client) GetSchemaWithCache(schemaId string) (*container.Container, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the cache mechanism generic to allow for anything to be cached with some sort of cache-id?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could but im just not sure if it makes sense, i dont see any other object in ndo that could grow as much as schema to need caching, the new tenant templates have pretty limited configuration options so we wouldnt gain much from caching them

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue two point to this:

  1. schema originally was not this big but it grew overtime, in some template structures like l3out templates there are already large configurations possible
  2. from a consistency point of view I think the caching should behave similar for every GET request

client/client.go Outdated
return word
}

type ThreadSafeCache struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why add this too client file instead of as separate cache file?
do we have a non thread safe cache? should we just name it cache and allow have thread-safe implementation for the methods/functions?

client/client.go Outdated
}

// DeletePattern removes all items from the cache matching the pattern.
func (c *ThreadSafeCache) DeletePattern(pattern string) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we also require a delete all or clear cache method/function?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is InvalidateAllSchemaCache that can be used to clear all cache in case it's needed - will change that to Clear to have it for all possible cache. We also have InvalidateSchemaCache that is used after every create/update operation on schema resources
not sure there is need for anything more, closing the terraform sessions should clear cache on its own

client/client.go Outdated
}

// Set adds or updates an item in the cache.
func (c *ThreadSafeCache) Set(key string, value interface{}) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the c is kind of confusing me with the c used for client, shall we make this more specific?

client/client.go Outdated
Comment on lines +722 to +725
items map[string]interface{}
hits int64
misses int64
invalidations int64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your exact goal of this statistics part? should we also include statistics per cache item?
If we want to expose statistics I think we should create a statistics struct which is part of each item, with the necessary data.

type ThreadSafeCache struct {
	mu            sync.RWMutex
	items         map[string]CacheItem
}

type CacheItem struct {
	item 		  interface{}
	hits          int64
	misses        int64
	invalidations int64
}

Perhaps even further where the statistics are also set in its own type.

Then have a cache statistics method/function that would provide a statistics report based on preference of statistics you would like to represent.

client/client.go Outdated
Comment on lines +471 to +473
hits, misses, invalidations, hitRatio := c.Cache.GetStats()
log.Printf("[DEBUG] SCHEMA_CACHE_INVALIDATED for %s | Stats: Hits=%d, Misses=%d, Invalidations=%d, HitRatio=%.1f%%",
schemaId, hits, misses, invalidations, hitRatio)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not leverage the LogCacheStats function, or some other wrapper function to prevent all the copied code?

README.md Outdated

```golang
// Fetch schema with caching support (automatically handles cache hits/misses)
schema, err := msoClient.GetSchemaWithCache("schema-id")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not adjust the current GetViaURL, so that when caching is enabled it leverage caching for everything?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now i wanted to enabled it on selected resources only that we know are problematic to see the behaviour

Copy link
Copy Markdown
Contributor

@akinross akinross Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think we should have consistent behaviour on all the resources, but this is not a resource (schema) discussion in my opinion but a generic caching discussion. In this case I would say we should have something like a GetViaURLWithCache function that can be used for schema ( but also for other endpoints ) where it would access the caching mechanism.

In the provider we can then gradually change those resources.

README.md Outdated
Comment on lines +74 to +84
// Invalidate a specific schema from cache (e.g., after updates)
msoClient.InvalidateSchemaCache("schema-id")

// Clear all cached schemas (useful for bulk operations or cleanup)
msoClient.InvalidateAllSchemaCache()

// Get cache statistics for monitoring
hits, misses, invalidations, hitRatio := msoClient.GetCacheStats()

// Log current cache statistics
msoClient.LogCacheStats()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these functions/methods should be part of a cache struct and not the client

@juchowan juchowan marked this pull request as draft December 22, 2025 15:07
@juchowan juchowan marked this pull request as ready for review December 23, 2025 13:06
@juchowan juchowan requested a review from akinross February 10, 2026 14:08
@github-actions github-actions bot changed the title add support for caching add support for caching (DCNE-680) Feb 10, 2026
Comment on lines +11 to +14
const (
KB = 1024
MB = 1024 * 1024
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these constants? could you provide come comment/context for them?

}
}

// Set adds or updates an item in the cache with per-item size tracking.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you introducing per item size tracking? is there a use case for this?

itemSize = int64(len(jsonBytes))
} else {
// Fallback for non-byte values
itemSize = 1024 // Estimate 1KB for unknown types
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use the defined constant for this?

Comment on lines +143 to +148
func CacheEnabled(enabled bool) Option {
return func(client *Client) {
client.cacheEnabled = enabled
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this required? can't we init the client with a cache when we provide that option? this way when a cache exist on the client it is used and else it is not

}

// detectURLResourceType detects resource type from URL for better logging and monitoring
func (c *Client) detectURLResourceType(url string) string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of this? I see a lot of logging / statistics things being added when I think we should first align on the caching mechanism itself. Is the url itself not clear enough in logging?

}

// Get gets and clones an item with per-item statistics tracking
func (cache *Cache) Get(key string, cloneFunc func(interface{}) (interface{}, error)) (interface{}, bool, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need clonefunc? not sure i really see the benefit of passing in a function like this, why not handle this cloning if required inside of the get itself?


cacheKey := url

passthroughFunc := func(item interface{}) (interface{}, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i do not understand what the benefit of the passthroughFunc is giving, could you please elaborate the intent of this?

c.Cache.LogEvent(resourceType+"_CACHED", url, true)
} else {
// Data was cached by another goroutine while we were fetching
log.Printf("[DEBUG] %s already cached by another goroutine for %s", resourceType, url)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also then check that the content of the cached item is the same? what happens when they are different?

Comment on lines +71 to +78
// Fetch data with caching support (automatically handles cache hits/misses)
data, err := msoClient.GetViaURLWithCache("/api/v1/schemas/schema-id")

// Invalidate a specific URL from cache (e.g., after updates)
msoClient.InvalidateURLCache("/api/v1/schemas/schema-id")

// Clear all cached items (useful for bulk operations or cleanup)
msoClient.ClearCache()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you propose to get the changes into the provider? like after this caching change has been added to the client, what is required in the provider to change in order to support it?


}

func (c *Client) GetViaURLWithCache(url string) (*container.Container, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this a a separate function? why not leverage the GetViaURL and detect if there is caching enabled there, this way none of the current provider code needs to be changed, you would keep functionality as is and then when somebody enables caching it would run that cache function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants