fix(cache): use key versioning (#10657)

# Which Problems Are Solved

Cached object may have a different schema between Zitadel versions.

# How the Problems Are Solved

Use the curent build version in DB based cache connectors PostgreSQL and
Redis.

# Additional Changes

- Cleanup the ZitadelVersion field from the authz Instance
- Solve potential race condition on global variables in build package.

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/10648
- Obsoletes https://github.com/zitadel/zitadel/pull/10646
- Needs to be back-ported to v4 over
https://github.com/zitadel/zitadel/pull/10645

(cherry picked from commit f6f37d3a31)
This commit is contained in:
Tim Möhlmann
2025-09-15 12:51:54 +03:00
committed by Livio Spring
parent f9b3c1ef50
commit 6e90d4a927
8 changed files with 127 additions and 87 deletions

View File

@@ -38,21 +38,23 @@ var (
)
type redisCache[I, K comparable, V cache.Entry[I, K]] struct {
db int
config *cache.Config
indices []I
connector *Connector
logger *slog.Logger
db int
zitadelVersion string
config *cache.Config
indices []I
connector *Connector
logger *slog.Logger
}
// NewCache returns a cache that stores and retrieves object using single Redis.
func NewCache[I, K comparable, V cache.Entry[I, K]](config cache.Config, client *Connector, db int, indices []I) cache.Cache[I, K, V] {
func NewCache[I, K comparable, V cache.Entry[I, K]](config cache.Config, zitadelVersion string, client *Connector, db int, indices []I) cache.Cache[I, K, V] {
return &redisCache[I, K, V]{
config: &config,
db: db,
indices: indices,
connector: client,
logger: config.Log.Slog(),
config: &config,
zitadelVersion: zitadelVersion,
db: db,
indices: indices,
connector: client,
logger: config.Log.Slog(),
}
}
@@ -166,7 +168,7 @@ func (c *redisCache[I, K, V]) Truncate(ctx context.Context) (err error) {
func (c *redisCache[I, K, V]) redisIndexKeys(index I, keys ...K) []string {
out := make([]string, len(keys))
for i, k := range keys {
out[i] = fmt.Sprintf("%v:%v", index, k)
out[i] = fmt.Sprintf("%s:%v:%v", c.zitadelVersion, index, k)
}
return out
}

View File

@@ -68,9 +68,9 @@ func Test_redisCache_set(t *testing.T) {
},
},
assertions: func(t *testing.T, s *miniredis.Miniredis, objectID string) {
s.CheckGet(t, "0:one", objectID)
s.CheckGet(t, "1:foo", objectID)
s.CheckGet(t, "1:bar", objectID)
s.CheckGet(t, "VERSION:0:one", objectID)
s.CheckGet(t, "VERSION:1:foo", objectID)
s.CheckGet(t, "VERSION:1:bar", objectID)
assert.Empty(t, s.HGet(objectID, "expiry"))
assert.JSONEq(t, `{"ID":"one","Name":["foo","bar"]}`, s.HGet(objectID, "object"))
},
@@ -88,9 +88,9 @@ func Test_redisCache_set(t *testing.T) {
},
},
assertions: func(t *testing.T, s *miniredis.Miniredis, objectID string) {
s.CheckGet(t, "0:one", objectID)
s.CheckGet(t, "1:foo", objectID)
s.CheckGet(t, "1:bar", objectID)
s.CheckGet(t, "VERSION:0:one", objectID)
s.CheckGet(t, "VERSION:1:foo", objectID)
s.CheckGet(t, "VERSION:1:bar", objectID)
assert.Empty(t, s.HGet(objectID, "expiry"))
assert.JSONEq(t, `{"ID":"one","Name":["foo","bar"]}`, s.HGet(objectID, "object"))
assert.Positive(t, s.TTL(objectID))
@@ -115,9 +115,9 @@ func Test_redisCache_set(t *testing.T) {
},
},
assertions: func(t *testing.T, s *miniredis.Miniredis, objectID string) {
s.CheckGet(t, "0:one", objectID)
s.CheckGet(t, "1:foo", objectID)
s.CheckGet(t, "1:bar", objectID)
s.CheckGet(t, "VERSION:0:one", objectID)
s.CheckGet(t, "VERSION:1:foo", objectID)
s.CheckGet(t, "VERSION:1:bar", objectID)
assert.NotEmpty(t, s.HGet(objectID, "expiry"))
assert.JSONEq(t, `{"ID":"one","Name":["foo","bar"]}`, s.HGet(objectID, "object"))
assert.Positive(t, s.TTL(objectID))
@@ -141,9 +141,9 @@ func Test_redisCache_set(t *testing.T) {
},
},
assertions: func(t *testing.T, s *miniredis.Miniredis, objectID string) {
s.CheckGet(t, "0:one", objectID)
s.CheckGet(t, "1:foo", objectID)
s.CheckGet(t, "1:bar", objectID)
s.CheckGet(t, "VERSION:0:one", objectID)
s.CheckGet(t, "VERSION:1:foo", objectID)
s.CheckGet(t, "VERSION:1:bar", objectID)
assert.Empty(t, s.HGet(objectID, "expiry"))
assert.JSONEq(t, `{"ID":"one","Name":["foo","bar"]}`, s.HGet(objectID, "object"))
assert.Positive(t, s.TTL(objectID))
@@ -710,7 +710,7 @@ func prepareCache(t *testing.T, conf cache.Config, options ...func(*Config)) (ca
connector.Close()
server.Close()
})
c := NewCache[testIndex, string, *testObject](conf, connector, testDB, testIndices)
c := NewCache[testIndex, string, *testObject](conf, "VERSION", connector, testDB, testIndices)
return c, server
}