Skip to content

Commit af4a5f8

Browse files
Add CacheLatestTtlSecs to allow expiration of latest schemas (#1106)
* chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * chore: update repo semaphore config * Add CacheLatestTtlSecs to allow expiration of latest schemas * Fix golint * Add runtime finalizer to stop ticker --------- Co-authored-by: Confluent Jenkins Bot <[email protected]>
1 parent 8d530db commit af4a5f8

File tree

5 files changed

+91
-1
lines changed

5 files changed

+91
-1
lines changed

schemaregistry/cache/cache.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type Cache interface {
3737
// Parameters:
3838
// * `key` - the key to delete
3939
Delete(key interface{})
40+
// Clear clears the cache
41+
Clear()
4042
// ToMap returns the current cache entries copied into a map
4143
ToMap() map[interface{}]interface{}
4244
}

schemaregistry/cache/lrucache.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,19 @@ func (c *LRUCache) Delete(key interface{}) {
131131
}
132132
}
133133

134+
// Clear clears the cache
135+
func (c *LRUCache) Clear() {
136+
c.cacheLock.Lock()
137+
for key, value := range c.lruElements {
138+
delete(c.lruElements, key)
139+
c.lruKeys.Remove(value)
140+
}
141+
for key := range c.entries {
142+
delete(c.entries, key)
143+
}
144+
c.cacheLock.Unlock()
145+
}
146+
134147
// ToMap returns the current cache entries copied into a map
135148
func (c *LRUCache) ToMap() map[interface{}]interface{} {
136149
ret := make(map[interface{}]interface{})

schemaregistry/cache/mapcache.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ func (c *MapCache) Delete(key interface{}) {
5757
delete(c.entries, key)
5858
}
5959

60+
// Clear clears the cache
61+
func (c *MapCache) Clear() {
62+
for key := range c.entries {
63+
delete(c.entries, key)
64+
}
65+
}
66+
6067
// ToMap returns the current cache entries copied into a map
6168
func (c *MapCache) ToMap() map[interface{}]interface{} {
6269
ret := make(map[interface{}]interface{})

schemaregistry/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type Config struct {
6262
RequestTimeoutMs int
6363
// CacheCapacity positive integer or zero for unbounded capacity
6464
CacheCapacity int
65+
// CacheLatestTTLSecs ttl in secs for caching the latest schema
66+
CacheLatestTTLSecs int
6567

6668
// HTTP client
6769
HTTPClient *http.Client

schemaregistry/schemaregistry_client.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"net/url"
23+
"runtime"
2324
"strings"
2425
"sync"
26+
"time"
2527

2628
"github.com/confluentinc/confluent-kafka-go/v2/schemaregistry/cache"
2729
)
@@ -189,6 +191,9 @@ type client struct {
189191
schemaToVersionCacheLock sync.RWMutex
190192
versionToSchemaCache cache.Cache
191193
versionToSchemaCacheLock sync.RWMutex
194+
latestToSchemaCache cache.Cache
195+
latestToSchemaCacheLock sync.RWMutex
196+
evictor *evictor
192197
}
193198

194199
var _ Client = new(client)
@@ -243,6 +248,7 @@ func NewClient(conf *Config) (Client, error) {
243248
var idToSchemaCache cache.Cache
244249
var schemaToVersionCache cache.Cache
245250
var versionToSchemaCache cache.Cache
251+
var latestToSchemaCache cache.Cache
246252
if conf.CacheCapacity != 0 {
247253
schemaToIDCache, err = cache.NewLRUCache(conf.CacheCapacity)
248254
if err != nil {
@@ -260,18 +266,28 @@ func NewClient(conf *Config) (Client, error) {
260266
if err != nil {
261267
return nil, err
262268
}
269+
latestToSchemaCache, err = cache.NewLRUCache(conf.CacheCapacity)
270+
if err != nil {
271+
return nil, err
272+
}
263273
} else {
264274
schemaToIDCache = cache.NewMapCache()
265275
idToSchemaCache = cache.NewMapCache()
266276
schemaToVersionCache = cache.NewMapCache()
267277
versionToSchemaCache = cache.NewMapCache()
278+
latestToSchemaCache = cache.NewMapCache()
268279
}
269280
handle := &client{
270281
restService: restService,
271282
schemaToIDCache: schemaToIDCache,
272283
idToSchemaCache: idToSchemaCache,
273284
schemaToVersionCache: schemaToVersionCache,
274285
versionToSchemaCache: versionToSchemaCache,
286+
latestToSchemaCache: latestToSchemaCache,
287+
}
288+
if conf.CacheLatestTTLSecs > 0 {
289+
runEvictor(handle, time.Duration(conf.CacheLatestTTLSecs)*time.Second)
290+
runtime.SetFinalizer(handle, stopEvictor)
275291
}
276292
return handle, nil
277293
}
@@ -393,7 +409,26 @@ func (c *client) GetID(subject string, schema SchemaInfo, normalize bool) (id in
393409
// GetLatestSchemaMetadata fetches latest version registered with the provided subject
394410
// Returns SchemaMetadata object
395411
func (c *client) GetLatestSchemaMetadata(subject string) (result SchemaMetadata, err error) {
396-
return c.GetSchemaMetadata(subject, -1)
412+
c.latestToSchemaCacheLock.RLock()
413+
metadataValue, ok := c.latestToSchemaCache.Get(subject)
414+
c.latestToSchemaCacheLock.RUnlock()
415+
if ok {
416+
return *metadataValue.(*SchemaMetadata), nil
417+
}
418+
419+
c.latestToSchemaCacheLock.Lock()
420+
// another goroutine could have already put it in cache
421+
metadataValue, ok = c.latestToSchemaCache.Get(subject)
422+
if !ok {
423+
err = c.restService.handleRequest(newRequest("GET", versions, nil, url.PathEscape(subject), "latest"), &result)
424+
if err == nil {
425+
c.latestToSchemaCache.Put(subject, &result)
426+
}
427+
} else {
428+
result = *metadataValue.(*SchemaMetadata)
429+
}
430+
c.latestToSchemaCacheLock.Unlock()
431+
return result, err
397432
}
398433

399434
// GetSchemaMetadata fetches the requested subject schema identified by version
@@ -687,3 +722,34 @@ func (c *client) UpdateDefaultCompatibility(update Compatibility) (compatibility
687722

688723
return result.CompatibilityUpdate, err
689724
}
725+
726+
type evictor struct {
727+
Interval time.Duration
728+
stop chan bool
729+
}
730+
731+
func (e *evictor) Run(c cache.Cache) {
732+
ticker := time.NewTicker(e.Interval)
733+
for {
734+
select {
735+
case <-ticker.C:
736+
c.Clear()
737+
case <-e.stop:
738+
ticker.Stop()
739+
return
740+
}
741+
}
742+
}
743+
744+
func stopEvictor(c *client) {
745+
c.evictor.stop <- true
746+
}
747+
748+
func runEvictor(c *client, ci time.Duration) {
749+
e := &evictor{
750+
Interval: ci,
751+
stop: make(chan bool),
752+
}
753+
c.evictor = e
754+
go e.Run(c.latestToSchemaCache)
755+
}

0 commit comments

Comments
 (0)