Skip to content

Commit b379bf2

Browse files
committed
feat: switch schematic cache to LRU and negative TTL
Fixes #408 Now we have proper cache capacity, LRU, and also negative cache TTL to help with case when multiple instance might disagree on whether cache contains not found or not. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent 0450038 commit b379bf2

7 files changed

Lines changed: 308 additions & 104 deletions

File tree

cmd/image-factory/cmd/options.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ type CacheOptions struct {
187187

188188
// S3 contains configuration for using S3 to store cached assets.
189189
S3 S3CacheOptions `koanf:"s3"`
190+
191+
// Schematic contains configuration for caching schematic blobs.
192+
Schematic SchematicCacheOptions `koanf:"schematic"`
190193
}
191194

192195
// CDNCacheOptions configures CDN-based cache for the image factory.
@@ -219,6 +222,15 @@ type S3CacheOptions struct {
219222
Enabled bool `koanf:"enabled"`
220223
}
221224

225+
// SchematicCacheOptions configures caching of schematic blobs.
226+
type SchematicCacheOptions struct {
227+
// Capacity sets the maximum number of schematics to keep in the in-memory cache.
228+
Capacity uint64 `koanf:"capacity"`
229+
230+
// NegativeTTL sets the time-to-live for negative cache entries (schematics not found in underlying storage).
231+
NegativeTTL time.Duration `koanf:"negativeTTL"`
232+
}
233+
222234
// ContainerSignature contains configuration for verifying container image signatures.
223235
type ContainerSignature struct {
224236
// SubjectRegExp is a regular expression used to validate the subject in container signatures.
@@ -386,6 +398,10 @@ var DefaultOptions = Options{
386398
S3: S3CacheOptions{
387399
Bucket: "image-factory",
388400
},
401+
Schematic: SchematicCacheOptions{
402+
Capacity: 100_000,
403+
NegativeTTL: 30 * time.Second,
404+
},
389405
},
390406

391407
Metrics: MetricsOptions{

cmd/image-factory/cmd/service.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ func RunFactory(ctx context.Context, logger *zap.Logger, opts Options) error {
7676

7777
defer artifactsManager.Close() //nolint:errcheck
7878

79-
configFactory, err := buildSchematicFactory(logger, opts)
79+
eg, ctx := errgroup.WithContext(ctx)
80+
81+
configFactory, err := buildSchematicFactory(ctx, logger, eg, opts)
8082
if err != nil {
8183
return err
8284
}
@@ -166,8 +168,6 @@ func RunFactory(ctx context.Context, logger *zap.Logger, opts Options) error {
166168

167169
insecure := opts.HTTP.CertFile == "" && opts.HTTP.KeyFile == ""
168170

169-
eg, ctx := errgroup.WithContext(ctx)
170-
171171
if !insecure {
172172
certLoader := cryptotls.NewDynamicCertificate(opts.HTTP.CertFile, opts.HTTP.KeyFile)
173173
if err = certLoader.Load(); err != nil {
@@ -395,7 +395,7 @@ func buildAssetBuilder(logger *zap.Logger, artifactsManager *artifacts.Manager,
395395
return builder, nil
396396
}
397397

398-
func buildSchematicFactory(logger *zap.Logger, opts Options) (*schematic.Factory, error) {
398+
func buildSchematicFactory(ctx context.Context, logger *zap.Logger, eg *errgroup.Group, opts Options) (*schematic.Factory, error) {
399399
var repoOpts []name.Option
400400

401401
if opts.Artifacts.Schematic.Insecure {
@@ -412,7 +412,23 @@ func buildSchematicFactory(logger *zap.Logger, opts Options) (*schematic.Factory
412412
return nil, fmt.Errorf("failed to initialize registry storage: %w", err)
413413
}
414414

415-
c := schematiccache.NewCache(storage, schematiccache.Options{MetricsNamespace: opts.Metrics.Namespace})
415+
c := schematiccache.NewCache(storage, schematiccache.Options{
416+
CacheCapacity: opts.Cache.Schematic.Capacity,
417+
NegativeTTL: opts.Cache.Schematic.NegativeTTL,
418+
MetricsNamespace: opts.Metrics.Namespace,
419+
})
420+
421+
eg.Go(func() error {
422+
return c.Start()
423+
})
424+
425+
eg.Go(func() error {
426+
<-ctx.Done()
427+
428+
c.Stop()
429+
430+
return nil
431+
})
416432

417433
factory := schematic.NewFactory(logger, c, schematic.Options{MetricsNamespace: opts.Metrics.Namespace})
418434

docs/configuration.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,30 @@ Enabled enables S3 cache.
319319

320320
---
321321

322+
### `cache.schematic`
323+
324+
Schematic contains configuration for caching schematic blobs.
325+
326+
---
327+
328+
### `cache.schematic.capacity`
329+
330+
- **Type:** `uint64`
331+
- **Env:** `CACHE_SCHEMATIC_CAPACITY`
332+
333+
Capacity sets the maximum number of schematics to keep in the in-memory cache.
334+
335+
---
336+
337+
### `cache.schematic.negativeTTL`
338+
339+
- **Type:** `time.Duration`
340+
- **Env:** `CACHE_SCHEMATIC_NEGATIVETTL`
341+
342+
NegativeTTL sets the time-to-live for negative cache entries (schematics not found in underlying storage).
343+
344+
---
345+
322346
### `metrics`
323347

324348
Metrics holds configuration for the Prometheus metrics endpoint.
@@ -775,6 +799,9 @@ cache:
775799
endpoint: ""
776800
insecure: false
777801
region: ""
802+
schematic:
803+
capacity: 100000
804+
negativeTTL: 30s
778805
signingKeyPath: ""
779806
containerSignature:
780807
disabled: false
@@ -850,6 +877,8 @@ IF_CACHE_S3_ENABLED=false
850877
IF_CACHE_S3_ENDPOINT=
851878
IF_CACHE_S3_INSECURE=false
852879
IF_CACHE_S3_REGION=
880+
IF_CACHE_SCHEMATIC_CAPACITY=100000
881+
IF_CACHE_SCHEMATIC_NEGATIVETTL=30s
853882
IF_CACHE_SIGNINGKEYPATH=
854883
IF_CONTAINERSIGNATURE_DISABLED=false
855884
IF_CONTAINERSIGNATURE_ISSUER=https://accounts.google.com

go.mod

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/google/go-containerregistry v0.20.7
88
github.com/h2non/filetype v1.1.3
99
github.com/hashicorp/go-cleanhttp v0.5.2
10+
github.com/jellydator/ttlcache/v3 v3.4.0
1011
github.com/julienschmidt/httprouter v1.3.0
1112
github.com/klauspost/compress v1.18.4
1213
github.com/knadh/koanf/parsers/dotenv v1.1.1
@@ -29,13 +30,14 @@ require (
2930
github.com/siderolabs/go-pointer v1.0.1
3031
github.com/siderolabs/talos v1.13.0-alpha.1.0.20260218120223-5df10f2604b5
3132
github.com/siderolabs/talos/pkg/machinery v1.13.0-alpha.1.0.20260218120223-5df10f2604b5
32-
github.com/sigstore/cosign/v3 v3.0.4
33+
github.com/sigstore/cosign/v3 v3.0.5
3334
github.com/sigstore/sigstore v1.10.4
3435
github.com/slok/go-http-metrics v0.13.0
3536
github.com/spf13/pflag v1.0.10
3637
github.com/stretchr/testify v1.11.1
3738
github.com/u-root/u-root v0.15.0
3839
github.com/ulikunitz/xz v0.5.15
40+
go.uber.org/goleak v1.3.0
3941
go.uber.org/zap v1.27.1
4042
go.yaml.in/yaml/v4 v4.0.0-rc.4
4143
golang.org/x/sync v0.19.0
@@ -154,7 +156,7 @@ require (
154156
github.com/google/nftables v0.3.0 // indirect
155157
github.com/google/uuid v1.6.0 // indirect
156158
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
157-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
159+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.5 // indirect
158160
github.com/hashicorp/errwrap v1.1.0 // indirect
159161
github.com/hashicorp/go-envparse v0.1.0 // indirect
160162
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -179,7 +181,7 @@ require (
179181
github.com/mdlayher/netlink v1.8.0 // indirect
180182
github.com/mdlayher/socket v0.5.1 // indirect
181183
github.com/mdp/qrterminal/v3 v3.2.1 // indirect
182-
github.com/miekg/pkcs11 v1.1.1 // indirect
184+
github.com/miekg/pkcs11 v1.1.2 // indirect
183185
github.com/minio/crc64nvme v1.1.0 // indirect
184186
github.com/minio/md5-simd v1.1.2 // indirect
185187
github.com/mitchellh/copystructure v1.2.0 // indirect
@@ -224,7 +226,7 @@ require (
224226
github.com/siderolabs/protoenc v0.2.4 // indirect
225227
github.com/sigstore/protobuf-specs v0.5.0 // indirect
226228
github.com/sigstore/rekor v1.5.0 // indirect
227-
github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect
229+
github.com/sigstore/rekor-tiles/v2 v2.2.0 // indirect
228230
github.com/sigstore/sigstore-go v1.1.4 // indirect
229231
github.com/sigstore/timestamp-authority/v2 v2.0.4 // indirect
230232
github.com/sirupsen/logrus v1.9.4 // indirect
@@ -242,7 +244,7 @@ require (
242244
github.com/vbatts/tar-split v0.12.2 // indirect
243245
github.com/vultr/metadata v1.1.0 // indirect
244246
github.com/x448/float16 v0.8.4 // indirect
245-
gitlab.com/gitlab-org/api/client-go v1.11.0 // indirect
247+
gitlab.com/gitlab-org/api/client-go v1.25.0 // indirect
246248
go.mongodb.org/mongo-driver v1.17.6 // indirect
247249
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
248250
go.opentelemetry.io/otel v1.39.0 // indirect
@@ -253,9 +255,9 @@ require (
253255
go.yaml.in/yaml/v3 v3.0.4 // indirect
254256
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
255257
golang.org/x/crypto v0.48.0 // indirect
256-
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
258+
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
257259
golang.org/x/mod v0.32.0 // indirect
258-
golang.org/x/net v0.50.0 // indirect
260+
golang.org/x/net v0.51.0 // indirect
259261
golang.org/x/oauth2 v0.35.0 // indirect
260262
golang.org/x/term v0.40.0 // indirect
261263
golang.org/x/time v0.14.0 // indirect
@@ -270,9 +272,9 @@ require (
270272
gopkg.in/yaml.v3 v3.0.1 // indirect
271273
gotest.tools v2.2.0+incompatible // indirect
272274
gotest.tools/v3 v3.5.1 // indirect
273-
k8s.io/api v0.35.0 // indirect
274-
k8s.io/apimachinery v0.35.0 // indirect
275-
k8s.io/client-go v0.35.0 // indirect
275+
k8s.io/api v0.35.1 // indirect
276+
k8s.io/apimachinery v0.35.1 // indirect
277+
k8s.io/client-go v0.35.1 // indirect
276278
k8s.io/klog/v2 v2.130.1 // indirect
277279
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
278280
k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect

0 commit comments

Comments
 (0)