Skip to content

Commit

Permalink
Update the docs with a section regarding the cache usage
Browse files Browse the repository at this point in the history
Signed-off-by: Soule BA <soule@weave.works>
  • Loading branch information
souleb committed Apr 2, 2022
1 parent 0f93028 commit 7ff96a8
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 14 deletions.
8 changes: 3 additions & 5 deletions controllers/helmchart_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *

// Try to retrieve the repository index from the cache
if r.Cache != nil {
if index, found := r.Cache.Get(r.Storage.LocalPath(*repo.GetArtifact())); err == nil {
if found {
chartRepo.Index = index.(*helmrepo.IndexFile)
}
if index, found := r.Cache.Get(r.Storage.LocalPath(*repo.GetArtifact())); found {
chartRepo.Index = index.(*helmrepo.IndexFile)
}
}

Expand Down Expand Up @@ -502,7 +500,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
// Using r.Storage.LocalPath(*repo.GetArtifact() is safe as the path is in the format /<helm-repository-name>/<chart-name>/<filename>.
err := r.Cache.Set(r.Storage.LocalPath(*repo.GetArtifact()), chartRepo.Index, r.TTL)
if err != nil {
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.CacheOperationFailedReason, "failed to cache index: %v", err)
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.CacheOperationFailedReason, "failed to cache index: %s", err)
}

}
Expand Down
47 changes: 47 additions & 0 deletions docs/spec/v1beta2/helmcharts.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,53 @@ Besides being reported in Events, the reconciliation errors are also logged by
the controller. The Flux CLI offer commands for filtering the logs for a
specific HelmChart, e.g. `flux logs --level=error --kind=HelmChart --name=<chart-name>`.

### Improving resource consumption by enabling the cache

When using a `HelmRepository` as Source for a `HelmChart`, the controller loads
the repository index in memory to find the latest version of the chart.

The controller can be configured to cache Helm repository indexes in memory.
The cache is used to avoid loading repository indexes for every `HelmChart`
reconciliation.

The following flags are provided to enable and configure the cache:
- `helm-cache-max-size`: The maximum size of the cache in number of indexes.
If `0`, then the cache is disabled.
- `helm-cache-ttl`: The TTL of an index in the cache.
- `helm-cache-purge-interval`: The interval at which the cache is purged of
expired items.

The caching strategy is to pull a repository index from the cache if it is
available, otherwise to load the index, retrieve and build the chart,
then cache the index. The cached index TTL is refreshed every time the
Helm repository index is loaded with the `helm-cache-ttl` value.

The cache is purged of expired items every `helm-cache-purge-interval`.

When the cache is full, no more items can be added to the cache, and the
source-controller will report a warning event instead.

In order to use the cache, set the related flags in the source-controller
Deployment config:

```yaml
spec:
containers:
- args:
- --watch-all-namespaces
- --log-level=info
- --log-encoding=json
- --enable-leader-election
- --storage-path=/data
- --storage-adv-addr=source-controller.$(RUNTIME_NAMESPACE).svc.cluster.local.
## Helm cache with up to 10 items, i.e. 10 indexes.
- --helm-cache-max-size=10
## TTL of an index is 1 hour.
- --helm-cache-ttl=1h
## Purge expired index every 10 minutes.
- --helm-cache-purge-interval=10m
```

## HelmChart Status

### Artifact
Expand Down
33 changes: 26 additions & 7 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ type Cache struct {

// Item is an item stored in the cache.
type Item struct {
Object interface{}
// Object is the item's value.
Object interface{}
// Expiration is the item's expiration time.
Expiration int64
}

type cache struct {
// Items holds the elements in the cache.
Items map[string]Item
// Maximum number of items the cache can hold.
// MaxItems is the maximum number of items the cache can hold.
MaxItems int
mu sync.RWMutex
janitor *janitor
Expand Down Expand Up @@ -82,6 +84,9 @@ func (c *cache) Set(key string, value interface{}, expiration time.Duration) err
return fmt.Errorf("Cache is full")
}

// Add an item to the cache, existing items will not be overwritten.
// To overwrite existing items, use Set.
// If the cache is full, Add will return an error.
func (c *cache) Add(key string, value interface{}, expiration time.Duration) error {
c.mu.Lock()
_, found := c.Items[key]
Expand All @@ -100,6 +105,8 @@ func (c *cache) Add(key string, value interface{}, expiration time.Duration) err
return fmt.Errorf("Cache is full")
}

// Get an item from the cache. Returns the item or nil, and a bool indicating
// whether the key was found.
func (c *cache) Get(key string) (interface{}, bool) {
c.mu.RLock()
item, found := c.Items[key]
Expand All @@ -117,18 +124,23 @@ func (c *cache) Get(key string) (interface{}, bool) {
return item.Object, true
}

// Delete an item from the cache. Does nothing if the key is not in the cache.
func (c *cache) Delete(key string) {
c.mu.Lock()
delete(c.Items, key)
c.mu.Unlock()
}

// Clear all items from the cache.
// This reallocate the inderlying array holding the items,
// so that the memory used by the items is reclaimed.
func (c *cache) Clear() {
c.mu.Lock()
c.Items = make(map[string]Item)
c.mu.Unlock()
}

// HasExpired returns true if the item has expired.
func (c *cache) HasExpired(key string) bool {
c.mu.RLock()
item, ok := c.Items[key]
Expand All @@ -146,6 +158,8 @@ func (c *cache) HasExpired(key string) bool {
return false
}

// SetExpiration sets the expiration for the given key.
// Does nothing if the key is not in the cache.
func (c *cache) SetExpiration(key string, expiration time.Duration) {
c.mu.Lock()
item, ok := c.Items[key]
Expand All @@ -157,6 +171,9 @@ func (c *cache) SetExpiration(key string, expiration time.Duration) {
c.mu.Unlock()
}

// GetExpiration returns the expiration for the given key.
// Returns zero if the key is not in the cache or the item
// has already expired.
func (c *cache) GetExpiration(key string) time.Duration {
c.mu.RLock()
item, ok := c.Items[key]
Expand All @@ -174,6 +191,7 @@ func (c *cache) GetExpiration(key string) time.Duration {
return time.Duration(item.Expiration - time.Now().UnixNano())
}

// DeleteExpired deletes all expired items from the cache.
func (c *cache) DeleteExpired() {
c.mu.Lock()
for k, v := range c.Items {
Expand All @@ -185,12 +203,12 @@ func (c *cache) DeleteExpired() {
}

type janitor struct {
Interval time.Duration
interval time.Duration
stop chan bool
}

func (j *janitor) Run(c *cache) {
ticker := time.NewTicker(j.Interval)
func (j *janitor) run(c *cache) {
ticker := time.NewTicker(j.interval)
for {
select {
case <-ticker.C:
Expand All @@ -206,20 +224,21 @@ func stopJanitor(c *Cache) {
c.janitor.stop <- true
}

// New creates a new cache with the given configuration.
func New(maxItems int, interval time.Duration) *Cache {
c := &cache{
Items: make(map[string]Item),
MaxItems: maxItems,
janitor: &janitor{
Interval: interval,
interval: interval,
stop: make(chan bool),
},
}

C := &Cache{c}

if interval > 0 {
go c.janitor.Run(c)
go c.janitor.run(c)
runtime.SetFinalizer(C, stopJanitor)
}

Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ func main() {
flag.DurationVar(&requeueDependency, "requeue-dependency", 30*time.Second,
"The interval at which failing dependencies are reevaluated.")
flag.IntVar(&helmCacheMaxSize, "helm-cache-max-size", 0,
"The maximum size of the cache in number of items.")
"The maximum size of the cache in number of indexes.")
flag.StringVar(&helmCacheTTL, "helm-cache-ttl", "15m",
"The TTL of an item in the cache. Valid time units are ns, us (or µs), ms, s, m, h.")
"The TTL of an index in the cache. Valid time units are ns, us (or µs), ms, s, m, h.")
flag.StringVar(&helmCachePurgeInterval, "helm-cache-purge-interval", "1m",
"The interval at which the cache is purged. Valid time units are ns, us (or µs), ms, s, m, h.")

Expand Down

0 comments on commit 7ff96a8

Please sign in to comment.