Skip to content

Commit 88b13b1

Browse files
authored
perf: filter workflows on K8s API during infractl list API calls (#1882)
1 parent a7a8b93 commit 88b13b1

9 files changed

Lines changed: 701 additions & 48 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ require (
8686
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
8787
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
8888
github.com/hashicorp/go-uuid v1.0.3 // indirect
89+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
8990
github.com/huandu/xstrings v1.5.0 // indirect
9091
github.com/inconshreveable/mousetrap v1.1.0 // indirect
9192
github.com/jackc/pgio v1.0.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDe
194194
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
195195
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
196196
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
197+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
198+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
197199
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
198200
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
199201
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cluster
2+
3+
import (
4+
"fmt"
5+
6+
lru "github.com/hashicorp/golang-lru/v2"
7+
"github.com/stackrox/infra/pkg/service/metrics"
8+
)
9+
10+
const (
11+
// Default cache size: estimate 10KB per artifact × 100 clusters × 2 artifacts = ~2MB
12+
// Setting to 1000 entries to allow for growth
13+
defaultCacheSize = 1000
14+
)
15+
16+
// artifactCache provides thread-safe LRU caching of immutable GCS artifact contents.
17+
// Since workflow artifacts don't change once written, no TTL is needed.
18+
type artifactCache struct {
19+
cache *lru.Cache[string, []byte]
20+
}
21+
22+
// newArtifactCache creates a new artifact cache with the specified size.
23+
func newArtifactCache(size int) (*artifactCache, error) {
24+
cache, err := lru.New[string, []byte](size)
25+
if err != nil {
26+
return nil, fmt.Errorf("failed to create LRU cache: %w", err)
27+
}
28+
29+
return &artifactCache{
30+
cache: cache,
31+
}, nil
32+
}
33+
34+
// Get retrieves cached artifact content if present.
35+
// Returns the content and true if found, nil and false otherwise.
36+
func (c *artifactCache) Get(bucket, key string) ([]byte, bool) {
37+
cacheKey := makeCacheKey(bucket, key)
38+
content, found := c.cache.Get(cacheKey)
39+
if !found {
40+
metrics.ArtifactCacheMissesCounter.Inc()
41+
return nil, false
42+
}
43+
44+
metrics.ArtifactCacheHitsCounter.Inc()
45+
return content, true
46+
}
47+
48+
// Set stores artifact content in the cache.
49+
func (c *artifactCache) Set(bucket, key string, content []byte) {
50+
cacheKey := makeCacheKey(bucket, key)
51+
c.cache.Add(cacheKey, content)
52+
metrics.ArtifactCacheSizeGauge.Set(float64(c.cache.Len()))
53+
}
54+
55+
// makeCacheKey creates a unique cache key from bucket and object key.
56+
func makeCacheKey(bucket, key string) string {
57+
return fmt.Sprintf("%s/%s", bucket, key)
58+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package cluster
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestArtifactCache_GetSet(t *testing.T) {
8+
cache, err := newArtifactCache(10)
9+
if err != nil {
10+
t.Fatalf("failed to create cache: %v", err)
11+
}
12+
13+
bucket := "test-bucket"
14+
key := "test-key"
15+
content := []byte("test content")
16+
17+
// Cache miss on first get
18+
_, found := cache.Get(bucket, key)
19+
if found {
20+
t.Error("expected cache miss, got hit")
21+
}
22+
23+
// Set content
24+
cache.Set(bucket, key, content)
25+
26+
// Cache hit on second get
27+
retrieved, found := cache.Get(bucket, key)
28+
if !found {
29+
t.Error("expected cache hit, got miss")
30+
}
31+
if string(retrieved) != string(content) {
32+
t.Errorf("expected content %q, got %q", content, retrieved)
33+
}
34+
}
35+
36+
func TestArtifactCache_MultipleEntries(t *testing.T) {
37+
cache, err := newArtifactCache(10)
38+
if err != nil {
39+
t.Fatalf("failed to create cache: %v", err)
40+
}
41+
42+
// Add multiple entries
43+
entries := map[string][]byte{
44+
"bucket1/key1": []byte("content1"),
45+
"bucket1/key2": []byte("content2"),
46+
"bucket2/key1": []byte("content3"),
47+
}
48+
49+
for key, content := range entries {
50+
cache.Set("bucket", key, content)
51+
}
52+
53+
// Verify all entries are cached
54+
for key, expectedContent := range entries {
55+
retrieved, found := cache.Get("bucket", key)
56+
if !found {
57+
t.Errorf("expected cache hit for key %q", key)
58+
}
59+
if string(retrieved) != string(expectedContent) {
60+
t.Errorf("for key %q, expected %q, got %q", key, expectedContent, retrieved)
61+
}
62+
}
63+
}
64+
65+
func TestArtifactCache_LRUEviction(t *testing.T) {
66+
cache, err := newArtifactCache(2) // Small cache for testing eviction
67+
if err != nil {
68+
t.Fatalf("failed to create cache: %v", err)
69+
}
70+
71+
// Fill cache to capacity
72+
cache.Set("bucket", "key1", []byte("content1"))
73+
cache.Set("bucket", "key2", []byte("content2"))
74+
75+
// Add one more entry, should evict oldest
76+
cache.Set("bucket", "key3", []byte("content3"))
77+
78+
// key1 should have been evicted
79+
_, found := cache.Get("bucket", "key1")
80+
if found {
81+
t.Error("expected key1 to be evicted")
82+
}
83+
84+
// key2 and key3 should still be present
85+
_, found = cache.Get("bucket", "key2")
86+
if !found {
87+
t.Error("expected key2 to be present")
88+
}
89+
90+
_, found = cache.Get("bucket", "key3")
91+
if !found {
92+
t.Error("expected key3 to be present")
93+
}
94+
}

0 commit comments

Comments
 (0)