Skip to content

Commit 0153b27

Browse files
committed
Improve caching logic
This commits changes the behaviour of the current caching logic for teams and the topology graph. We are using a similar logic as it was intorduced for the plugin templates. This means that the teams and topology data isn't generated in an additional goroutine, instead we get the data on the first request and then we cache it for the defined cache duration.
1 parent 3f7469e commit 0153b27

File tree

6 files changed

+269
-223
lines changed

6 files changed

+269
-223
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan
3333
- [#53](https://github.com/kobsio/kobs/pull/53): Improve Jaeger plugin, by allow filtering of services and operations and adding several actions for traces.
3434
- [#55](https://github.com/kobsio/kobs/pull/55): Allow a user to add a tag from a span as filter in the Jaeger plugin.
3535
- [#57](https://github.com/kobsio/kobs/pull/57): Visualize the offset of spans in the Jaeger plugin.
36+
- [#61](https://github.com/kobsio/kobs/pull/61): Improve caching logic, by generating the teams and topology chart only when it is requested and not via an additional goroutine.
3637

3738
## [v0.2.0](https://github.com/kobsio/kobs/releases/tag/v0.2.0) (2021-04-23)
3839

docs/resources/templates.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ In the following you can found the specification for the Template CRD.
2222
| name | string | The name of the variable. | Yes |
2323
| description | string | A description for the variable. | Yes |
2424

25-
## Example
25+
## Examples
2626

2727
!!! note
2828
We collect several templates in the [`deploy/templates`](https://github.com/kobsio/kobs/blob/main/deploy/templates) folder. If you have a template, which can also be useful for others, feel free to add it to this folder.

pkg/api/plugins/clusters/cluster/cluster.go

+2-19
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,17 @@ type Cluster struct {
3636
applicationClientset *applicationClientsetVersioned.Clientset
3737
teamClientset *teamClientsetVersioned.Clientset
3838
templateClientset *templateClientsetVersioned.Clientset
39-
options Options
4039
name string
4140
crds []*clustersProto.CRD
4241
}
4342

44-
// Options contains various options, which could be set for a cluster. For example a user can set the cache duration for
45-
// loaded manifest files and the names of the datasources, which should be used within a cluster.
46-
type Options struct {
47-
cacheDuration time.Duration
48-
}
49-
5043
// Cache implements a simple caching layer, for the loaded manifest files. The goal of the caching layer is to return
5144
// the manifests faster to the user.
5245
type Cache struct {
5346
namespaces []string
5447
namespacesLastFetch time.Time
5548
}
5649

57-
// SetOptions is used to set the options for a cluster. The options are not set during the creation of a cluster, so
58-
// that we do not have to pass around the options through different functions.
59-
// We also do not know the datasources befor the cluster name is determined, so that we loop through all loaded clusters
60-
// and connect the datasource names with the correct cluster.
61-
func (c *Cluster) SetOptions(cacheDuration time.Duration) {
62-
c.options = Options{
63-
cacheDuration: cacheDuration,
64-
}
65-
}
66-
6750
// GetName returns the name of the cluster.
6851
func (c *Cluster) GetName() string {
6952
return c.name
@@ -77,10 +60,10 @@ func (c *Cluster) GetCRDs() []*clustersProto.CRD {
7760
// GetNamespaces returns all namespaces for the cluster. To reduce the latency and the number of API calls, we are
7861
// "caching" the namespaces. This means that if a new namespace is created in a cluster, this namespaces is only shown
7962
// after the configured cache duration.
80-
func (c *Cluster) GetNamespaces(ctx context.Context) ([]string, error) {
63+
func (c *Cluster) GetNamespaces(ctx context.Context, cacheDuration time.Duration) ([]string, error) {
8164
log.WithFields(logrus.Fields{"last fetch": c.cache.namespacesLastFetch}).Tracef("Last namespace fetch.")
8265

83-
if c.cache.namespacesLastFetch.After(time.Now().Add(-1 * c.options.cacheDuration)) {
66+
if c.cache.namespacesLastFetch.After(time.Now().Add(-1 * cacheDuration)) {
8467
log.WithFields(logrus.Fields{"cluster": c.name}).Debugf("Return namespaces from cache.")
8568

8669
return c.cache.namespaces, nil

pkg/api/plugins/clusters/clusters.go

+93-77
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/kobsio/kobs/pkg/api/plugins/clusters/cluster"
1313
clustersProto "github.com/kobsio/kobs/pkg/api/plugins/clusters/proto"
1414
"github.com/kobsio/kobs/pkg/api/plugins/clusters/provider"
15-
teamProto "github.com/kobsio/kobs/pkg/api/plugins/team/proto"
1615
templateProto "github.com/kobsio/kobs/pkg/api/plugins/template/proto"
1716

1817
"github.com/sirupsen/logrus"
@@ -23,28 +22,37 @@ import (
2322

2423
var (
2524
log = logrus.WithFields(logrus.Fields{"package": "clusters"})
26-
cacheDurationNamespaces string
27-
cacheDurationTopology string
28-
cacheDurationTeams string
25+
cacheDurationNamespaces time.Duration
26+
cacheDurationTopology time.Duration
27+
cacheDurationTeams time.Duration
2928
cacheDurationTemplates time.Duration
3029
forbiddenResources []string
3130
)
3231

3332
// init is used to define all command-line flags for the clusters package.
3433
func init() {
35-
defaultCacheDurationNamespaces := "5m"
34+
defaultCacheDurationNamespaces := time.Duration(5 * time.Minute)
3635
if os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_NAMESPACES") != "" {
37-
defaultCacheDurationNamespaces = os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_NAMESPACES")
36+
parsedCacheDurationNamespaces, err := time.ParseDuration(os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_NAMESPACES"))
37+
if err == nil {
38+
defaultCacheDurationNamespaces = parsedCacheDurationNamespaces
39+
}
3840
}
3941

40-
defaultCacheDurationTopology := "60m"
42+
defaultCacheDurationTopology := time.Duration(60 * time.Minute)
4143
if os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_TOPOLOGY") != "" {
42-
defaultCacheDurationTopology = os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_TOPOLOGY")
44+
parsedCacheDurationTopology, err := time.ParseDuration(os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_TOPOLOGY"))
45+
if err == nil {
46+
defaultCacheDurationTopology = parsedCacheDurationTopology
47+
}
4348
}
4449

45-
defaultCacheDurationTeams := "60m"
50+
defaultCacheDurationTeams := time.Duration(60 * time.Minute)
4651
if os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_TEAMS") != "" {
47-
defaultCacheDurationTeams = os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_TEAMS")
52+
parsedCacheDurationTeams, err := time.ParseDuration(os.Getenv("KOBS_CLUSTERS_CACHE_DURATION_TEAMS"))
53+
if err == nil {
54+
defaultCacheDurationTeams = parsedCacheDurationTeams
55+
}
4856
}
4957

5058
defaultCacheDurationTemplates := time.Duration(60 * time.Minute)
@@ -60,9 +68,9 @@ func init() {
6068
defaultForbiddenResources = strings.Split(os.Getenv("KOBS_CLUSTERS_FORBIDDEN_RESOURCES"), ",")
6169
}
6270

63-
flag.StringVar(&cacheDurationNamespaces, "clusters.cache-duration.namespaces", defaultCacheDurationNamespaces, "The duration, for how long requests to get the list of namespaces should be cached.")
64-
flag.StringVar(&cacheDurationTopology, "clusters.cache-duration.topology", defaultCacheDurationTopology, "The duration, for how long the topology data should be cached.")
65-
flag.StringVar(&cacheDurationTeams, "clusters.cache-duration.teams", defaultCacheDurationTeams, "The duration, for how long the teams data should be cached.")
71+
flag.DurationVar(&cacheDurationNamespaces, "clusters.cache-duration.namespaces", defaultCacheDurationNamespaces, "The duration, for how long requests to get the list of namespaces should be cached.")
72+
flag.DurationVar(&cacheDurationTopology, "clusters.cache-duration.topology", defaultCacheDurationTopology, "The duration, for how long the topology data should be cached.")
73+
flag.DurationVar(&cacheDurationTeams, "clusters.cache-duration.teams", defaultCacheDurationTeams, "The duration, for how long the teams data should be cached.")
6674
flag.DurationVar(&cacheDurationTemplates, "clusters.cache-duration.templates", defaultCacheDurationTemplates, "The duration, for how long the templates data should be cached.")
6775
flag.StringArrayVar(&forbiddenResources, "clusters.forbidden-resources", defaultForbiddenResources, "A list of resources, which can not be accessed via kobs.")
6876
}
@@ -88,17 +96,23 @@ type Config struct {
8896
type Clusters struct {
8997
clustersProto.UnimplementedClustersServer
9098
clusters []*cluster.Cluster
91-
edges []*clustersProto.Edge
92-
nodes []*clustersProto.Node
93-
teams []Team
9499
cache Cache
95100
}
96101

97102
type Cache struct {
103+
topology Topology
104+
topologyLastFetch time.Time
105+
teams []Team
106+
teamsLastFetch time.Time
98107
templates []*templateProto.Template
99108
templatesLastFetch time.Time
100109
}
101110

111+
type Topology struct {
112+
edges []*clustersProto.Edge
113+
nodes []*clustersProto.Node
114+
}
115+
102116
func (c *Clusters) getCluster(name string) *cluster.Cluster {
103117
for _, cl := range c.clusters {
104118
if cl.GetName() == name {
@@ -148,7 +162,7 @@ func (c *Clusters) GetNamespaces(ctx context.Context, getNamespacesRequest *clus
148162
return nil, fmt.Errorf("invalid cluster name")
149163
}
150164

151-
clusterNamespaces, err := cluster.GetNamespaces(ctx)
165+
clusterNamespaces, err := cluster.GetNamespaces(ctx, cacheDurationNamespaces)
152166
if err != nil {
153167
return nil, err
154168
}
@@ -172,7 +186,7 @@ func (c *Clusters) GetNamespaces(ctx context.Context, getNamespacesRequest *clus
172186
return uniqueNamespaces[i] < uniqueNamespaces[j]
173187
})
174188

175-
log.WithFields(logrus.Fields{"namespaces": uniqueNamespaces}).Tracef("GetNamespaces")
189+
log.WithFields(logrus.Fields{"namespaces": len(uniqueNamespaces)}).Tracef("GetNamespaces")
176190

177191
return &clustersProto.GetNamespacesResponse{
178192
Namespaces: uniqueNamespaces,
@@ -337,20 +351,34 @@ func (c *Clusters) GetApplication(ctx context.Context, getApplicationRequest *cl
337351
func (c *Clusters) GetTeams(ctx context.Context, getTeamsRequest *clustersProto.GetTeamsRequest) (*clustersProto.GetTeamsResponse, error) {
338352
log.Tracef("GetTeams")
339353

340-
var teams []*teamProto.Team
354+
if c.cache.teamsLastFetch.After(time.Now().Add(-1 * cacheDurationTeams)) {
355+
return &clustersProto.GetTeamsResponse{
356+
Teams: transformCachedTeams(c.cache.teams),
357+
}, nil
358+
}
359+
360+
if c.cache.teams == nil {
361+
teams := getTeams(ctx, c.clusters)
362+
if teams != nil {
363+
c.cache.teamsLastFetch = time.Now()
364+
c.cache.teams = teams
365+
}
341366

342-
for _, team := range c.teams {
343-
teams = append(teams, &teamProto.Team{
344-
Name: team.Name,
345-
Description: team.Description,
346-
Logo: team.Logo,
347-
})
367+
return &clustersProto.GetTeamsResponse{
368+
Teams: transformCachedTeams(teams),
369+
}, nil
348370
}
349371

350-
log.WithFields(logrus.Fields{"count": len(teams)}).Tracef("GetTeams")
372+
go func() {
373+
teams := getTeams(ctx, c.clusters)
374+
if teams != nil {
375+
c.cache.teamsLastFetch = time.Now()
376+
c.cache.teams = teams
377+
}
378+
}()
351379

352380
return &clustersProto.GetTeamsResponse{
353-
Teams: teams,
381+
Teams: transformCachedTeams(c.cache.teams),
354382
}, nil
355383
}
356384

@@ -365,7 +393,15 @@ func (c *Clusters) GetTeams(ctx context.Context, getTeamsRequest *clustersProto.
365393
func (c *Clusters) GetTeam(ctx context.Context, getTeamRequest *clustersProto.GetTeamRequest) (*clustersProto.GetTeamResponse, error) {
366394
log.WithFields(logrus.Fields{"name": getTeamRequest.Name}).Tracef("GetTeam")
367395

368-
teamShort := getTeamData(c.teams, getTeamRequest.Name)
396+
if c.cache.teams == nil {
397+
teams := getTeams(ctx, c.clusters)
398+
if teams != nil {
399+
c.cache.teamsLastFetch = time.Now()
400+
c.cache.teams = teams
401+
}
402+
}
403+
404+
teamShort := getTeamData(c.cache.teams, getTeamRequest.Name)
369405
if teamShort == nil {
370406
return nil, fmt.Errorf("invalid team name")
371407
}
@@ -444,50 +480,42 @@ func (c *Clusters) GetTemplates(ctx context.Context, getTemplatesRequest *cluste
444480
// GetApplicationsTopology returns the topology for the given list of clusters and namespaces. We add an additional node
445481
// for each cluster and namespace. These nodes are used to group the applications by the cluster and namespace.
446482
func (c *Clusters) GetApplicationsTopology(ctx context.Context, getApplicationsTopologyRequest *clustersProto.GetApplicationsTopologyRequest) (*clustersProto.GetApplicationsTopologyResponse, error) {
447-
var edges []*clustersProto.Edge
448-
var nodes []*clustersProto.Node
449-
450-
for _, clusterName := range getApplicationsTopologyRequest.Clusters {
451-
nodes = append(nodes, &clustersProto.Node{
452-
Id: clusterName,
453-
Label: clusterName,
454-
Type: "cluster",
455-
Parent: "",
456-
Cluster: clusterName,
457-
Namespace: "",
458-
Name: "",
459-
})
460-
461-
for _, namespace := range getApplicationsTopologyRequest.Namespaces {
462-
nodes = append(nodes, &clustersProto.Node{
463-
Id: clusterName + "-" + namespace,
464-
Label: namespace,
465-
Type: "namespace",
466-
Parent: clusterName,
467-
Cluster: clusterName,
468-
Namespace: namespace,
469-
Name: "",
470-
})
483+
log.Tracef("GetApplicationsTopology")
471484

472-
for _, edge := range c.edges {
473-
if (edge.SourceCluster == clusterName && edge.SourceNamespace == namespace) || (edge.TargetCluster == clusterName && edge.TargetNamespace == namespace) {
474-
edges = appendEdgeIfMissing(edges, edge)
475-
}
476-
}
477-
}
485+
if c.cache.topologyLastFetch.After(time.Now().Add(-1 * cacheDurationTopology)) {
486+
edges, nodes := generateTopology(c.cache.topology, getApplicationsTopologyRequest.Clusters, getApplicationsTopologyRequest.Namespaces)
487+
return &clustersProto.GetApplicationsTopologyResponse{
488+
Edges: edges,
489+
Nodes: nodes,
490+
}, nil
478491
}
479492

480-
for _, edge := range edges {
481-
for _, node := range c.nodes {
482-
if node.Id == edge.Source || node.Id == edge.Target {
483-
nodes = appendNodeIfMissing(nodes, node)
484-
}
493+
if c.cache.topology.nodes == nil {
494+
topology := getTopology(ctx, c.clusters)
495+
if topology.nodes != nil {
496+
c.cache.topologyLastFetch = time.Now()
497+
c.cache.topology = topology
485498
}
499+
500+
edges, nodes := generateTopology(topology, getApplicationsTopologyRequest.Clusters, getApplicationsTopologyRequest.Namespaces)
501+
502+
return &clustersProto.GetApplicationsTopologyResponse{
503+
Edges: edges,
504+
Nodes: nodes,
505+
}, nil
486506
}
487507

508+
go func() {
509+
topology := getTopology(ctx, c.clusters)
510+
if topology.nodes != nil {
511+
c.cache.topologyLastFetch = time.Now()
512+
c.cache.topology = topology
513+
}
514+
}()
515+
488516
return &clustersProto.GetApplicationsTopologyResponse{
489-
Edges: edges,
490-
Nodes: nodes,
517+
Edges: c.cache.topology.edges,
518+
Nodes: c.cache.topology.nodes,
491519
}, nil
492520
}
493521

@@ -508,21 +536,9 @@ func Load(config Config) (*Clusters, error) {
508536
}
509537
}
510538

511-
d, err := time.ParseDuration(cacheDurationNamespaces)
512-
if err != nil {
513-
return nil, err
514-
}
515-
516-
for _, c := range clusters {
517-
c.SetOptions(d)
518-
}
519-
520539
cs := &Clusters{
521540
clusters: clusters,
522541
}
523542

524-
go cs.generateTopology()
525-
go cs.generateTeams()
526-
527543
return cs, nil
528544
}

0 commit comments

Comments
 (0)