Skip to content

Commit 33109de

Browse files
author
Tim Middleton
committed
Add configurable pruneFactor
1 parent 0e6fe9a commit 33109de

File tree

9 files changed

+130
-23
lines changed

9 files changed

+130
-23
lines changed

coherence/coherence_test_helpers.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ func GetSessionQueueID(session *Session, queue string) *int32 {
122122
return session.getQueueID(queue)
123123
}
124124

125+
func GetNearCachePruneFactor[K comparable, V any](namedMap NamedMap[K, V]) float32 {
126+
ncOptions := namedMap.getBaseClient().cacheOpts.NearCacheOptions
127+
if ncOptions == nil {
128+
return 0.0
129+
}
130+
131+
return ncOptions.PruneFactor
132+
}
133+
125134
// revive:disable:unexported-return
126135
func GetKeyListenerGroupMap[K comparable, V any](namedMap NamedMap[K, V]) map[K]*listenerGroupV1[K, V] {
127136
return namedMap.getBaseClient().keyListenersV1

coherence/common.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,26 @@ type CacheOptions struct {
103103

104104
// NearCacheOptions defines options when creating a near cache.
105105
type NearCacheOptions struct {
106-
TTL time.Duration
107-
HighUnits int64
108-
HighUnitsMemory int64
106+
// TTL is the maximum time to keep the entry in the near cache. When this time has been reached it will be expired.
107+
TTL time.Duration
108+
109+
// HighUnits is the maximum number of cache entries to keep in the near cache.
110+
HighUnits int64
111+
112+
// HighUnitsMemory is the maximum amount of memory to use for entries in the near cache.
113+
HighUnitsMemory int64
114+
109115
InvalidationStrategy InvalidationStrategyType // currently only supports ListenAll
116+
117+
// PruneFactor indicates the percentage of the total number of units that will remain
118+
// after the cache manager prunes the near cache(i.e. this is the "low watermark" value)
119+
// this value is in the range 0.1 to 1.0 and the default is 0.8 or 80%.
120+
PruneFactor float32
110121
}
111122

112123
func (n NearCacheOptions) String() string {
113-
return fmt.Sprintf("NearCacheOptions{TTL=%v, HighUnits=%v, HighUnitsMemory=%v, invalidationStrategy=%v}",
114-
n.TTL, n.HighUnits, n.HighUnitsMemory, getInvalidationStrategyString(n.InvalidationStrategy))
124+
return fmt.Sprintf("NearCacheOptions{TTL=%v, HighUnits=%v, HighUnitsMemory=%v, PruneFactor=%.2f, invalidationStrategy=%v}",
125+
n.TTL, n.HighUnits, n.HighUnitsMemory, n.PruneFactor, getInvalidationStrategyString(n.InvalidationStrategy))
115126
}
116127

117128
// WithExpiry returns a function to set the default expiry for a [NamedCache]. This option is not valid on [NamedMap].

coherence/doc.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -774,9 +774,13 @@ The following example shows how to get a named cache that will cache entries fro
774774
2. Creating a Near Cache specifying maximum number of entries to store
775775
776776
The following example shows how to get a named cache that will cache up to 100 entries from Get() or GetAll().
777-
When the threshold of HighUnits is reached, the near cache is pruned to 80% of its size and evicts least recently
777+
When the threshold of HighUnits is reached, the near cache is pruned to the default of 80% of its size and evicts least recently
778778
accessed and created entries.
779779
780+
Note: The default prune percentage is 0.8 (80%) which indicates the percentage of the total number of
781+
units that will remain after the cache manager prunes the near cache( i.e. this is the "low watermark" value).
782+
This can be changed by setting the PruneFactory to a value in the range 0.1 to 1.0 in [NearCacheOptions].
783+
780784
// specify HighUnits of 1000
781785
nearCacheOptions := coherence.NearCacheOptions{HighUnits: 1000}
782786
@@ -827,8 +831,8 @@ accessed and created entries.
827831
828832
// print the near cache stats via String()
829833
fmt.Println(namedMap.GetNearCacheStats())
830-
// localCache{name=customers options=localCacheOptions{ttl=0s, highUnits=0, highUnitsMemory=10.0KB, invalidation=ListenAll},
831-
// stats=CacheStats{puts=5000, gets=5000, hits=0, misses=5000, missesDuration=4.95257111s, hitRate=0, prunes=7, prunesDuration=196.498µs, size=398, memoryUsed=9.3KB}}
834+
// localCache{name=my-near-cache-high-units, options=localCacheOptions{ttl=0s, highUnits=1000, highUnitsMemory=0B, pruneFactor=0.80, invalidation=ListenAll}, stats=CacheStats{puts=1001, gets=1002, hits=1, misses=1001, missesDuration=4.628931138s,
835+
// hitRate=0.0998004, prunes=1, prunesDuration=181.533µs, expires=0, expiresDuration=0s, size=200, memoryUsed=53.2KB}}
832836
833837
[Coherence Documentation]: https://docs.oracle.com/en/middleware/standalone/coherence/14.1.1.2206/develop-applications/introduction-coherence-caches.html
834838
[examples]: https://github.com/oracle/coherence-go-client/tree/main/examples

coherence/localcache.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ var (
2424
)
2525

2626
const (
27-
KB = 1024
28-
MB = KB * KB
29-
GB = MB * KB
30-
prunePercent = 20
27+
KB = 1024
28+
MB = KB * KB
29+
GB = MB * KB
30+
defaultPruneFactor float32 = 0.8 // prune to 80%
3131
)
3232

3333
// localCache implements a local cache of values.
@@ -271,9 +271,9 @@ func (l *localCacheImpl[K, V]) pruneEntries() {
271271
l.registerPruneNanos(time.Since(start).Nanoseconds())
272272
}()
273273

274-
entriesToDelete := int(math.Round(float64(currentCacheSize * prunePercent / 100.0)))
274+
entriesToDelete := int(math.Round(float64(float32(currentCacheSize) * (1 - l.options.PruneFactor))))
275275

276-
// prune to default of 80% of the cache size.
276+
// prune to default of l.options.PruneFactor % of the cache size.
277277
// we first sort the map by lastAccess time / then insert time, so we remove all
278278
// entries firstly that have never been accessed.
279279
index := 0
@@ -329,6 +329,10 @@ func newLocalCache[K comparable, V any](name string, options ...func(localCache
329329
f(cache.options)
330330
}
331331

332+
if cache.options.PruneFactor == 0 {
333+
cache.options.PruneFactor = defaultPruneFactor
334+
}
335+
332336
return cache
333337
}
334338

@@ -338,11 +342,12 @@ type localCacheOptions struct {
338342
HighUnits int64
339343
HighUnitsMemory int64
340344
InvalidationStrategy InvalidationStrategyType
345+
PruneFactor float32
341346
}
342347

343348
func (o *localCacheOptions) String() string {
344-
return fmt.Sprintf("localCacheOptions{ttl=%v, highUnits=%v, highUnitsMemory=%v, invalidation=%v}",
345-
o.TTL, o.HighUnits, formatMemory(o.HighUnitsMemory), getInvalidationStrategyString(o.InvalidationStrategy))
349+
return fmt.Sprintf("localCacheOptions{ttl=%v, highUnits=%v, highUnitsMemory=%v, pruneFactor=%.2f, invalidation=%v}",
350+
o.TTL, o.HighUnits, formatMemory(o.HighUnitsMemory), o.PruneFactor, getInvalidationStrategyString(o.InvalidationStrategy))
346351
}
347352

348353
// withLocalCacheExpiry returns a function to set the expiry time for a local cache.
@@ -373,6 +378,13 @@ func withLocalCacheHighUnitsMemory(highUnitsMemory int64) func(options *localCac
373378
}
374379
}
375380

381+
// withPruneFactor returns a function to set the prune factor for a local cache.
382+
func withPruneFactor(pruneFactor float32) func(options *localCacheOptions) {
383+
return func(o *localCacheOptions) {
384+
o.PruneFactor = pruneFactor
385+
}
386+
}
387+
376388
func (l *localCacheImpl[K, V]) registerHit() {
377389
atomic.AddInt64(&l.cacheHits, 1)
378390
}

coherence/localcache_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package coherence
88
import (
99
"fmt"
1010
"github.com/onsi/gomega"
11+
"math"
1112
"sync"
1213
"testing"
1314
"time"
@@ -148,7 +149,9 @@ func TestLocalCacheWithHighUnitsOnly(t *testing.T) {
148149
// put a new entry which should cause prune of 20 entries
149150
cache.Put(100, "one hundred")
150151

151-
g.Expect(cache.Size()).To(gomega.Equal(80))
152+
expectedSize := int(math.Round(float64(float32(100) * cache.options.PruneFactor)))
153+
154+
g.Expect(cache.Size()).To(gomega.Equal(expectedSize))
152155
g.Expect(cache.GetCachePrunes()).To(gomega.Equal(int64(1)))
153156
fmt.Println(cache)
154157
}
@@ -189,7 +192,9 @@ func TestLocalCacheWithHighUnitsOnlyAccessTime(t *testing.T) {
189192
// put a new entry which should cause prune of 20 entries
190193
cache.Put(100, "one hundred")
191194

192-
g.Expect(cache.Size()).To(gomega.Equal(80))
195+
expectedSize := int(math.Round(float64(float32(100) * cache.options.PruneFactor)))
196+
197+
g.Expect(cache.Size()).To(gomega.Equal(expectedSize))
193198
g.Expect(cache.GetCachePrunes()).To(gomega.Equal(int64(1)))
194199

195200
// entries 1, 2 and three should not be removed as they were accessed

coherence/named_cache_client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,10 @@ func validateNearCacheOptions(options *NearCacheOptions) error {
839839
return ErrNegativeNearCacheOptions
840840
}
841841

842+
if options.PruneFactor != 0 && options.PruneFactor < 0.1 || options.PruneFactor > 1 {
843+
return ErrInvalidPruneFactor
844+
}
845+
842846
if options.TTL == 0 && options.HighUnits == 0 && options.HighUnitsMemory == 0 {
843847
return ErrInvalidNearCache
844848
}

coherence/named_map_client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,14 @@ func newBaseClient[K comparable, V any](session *Session, name string, format st
10301030
if bc.cacheOpts.NearCacheOptions.HighUnitsMemory != 0 {
10311031
options = append(options, withLocalCacheHighUnitsMemory(bc.cacheOpts.NearCacheOptions.HighUnitsMemory))
10321032
}
1033+
1034+
// default prune to defaultPruneFactor (80%) if not set
1035+
if bc.cacheOpts.NearCacheOptions.PruneFactor == 0 {
1036+
bc.cacheOpts.NearCacheOptions.PruneFactor = defaultPruneFactor
1037+
}
1038+
1039+
options = append(options, withPruneFactor(bc.cacheOpts.NearCacheOptions.PruneFactor))
1040+
10331041
nearCache := newLocalCache[K, V](bc.name, options...)
10341042
bc.nearCache = nearCache
10351043
}

coherence/session.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
ErrInvalidNearCacheWithTTL = errors.New("when using TTL for near cache you can only specify highUnits or highUnitsMemory")
3737
ErrInvalidNearCacheWithNoTTL = errors.New("you can only specify highUnits or highUnitsMemory, not both")
3838
ErrNegativeNearCacheOptions = errors.New("you cannot specify negative values for near cache options")
39+
ErrInvalidPruneFactor = errors.New("prune factory must be between 0 and 1.0")
3940
)
4041

4142
const (

test/e2e/standalone/near_cache_test.go

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/oracle/coherence-go-client/coherence/filters"
1414
"github.com/oracle/coherence-go-client/coherence/processors"
1515
"github.com/oracle/coherence-go-client/test/utils"
16+
"math"
1617
"testing"
1718
"time"
1819
)
@@ -33,6 +34,7 @@ func TestNearCacheOperationsAgainstMapAndCache(t *testing.T) {
3334
nearCacheOptions120Seconds := coherence.NearCacheOptions{TTL: time.Duration(120) * time.Second}
3435
nearCacheOptionsHighUnits1 := coherence.NearCacheOptions{HighUnits: 100}
3536
nearCacheOptionsHighUnits2 := coherence.NearCacheOptions{HighUnitsMemory: 50 * 1024}
37+
nearCacheOptionsHighUnits3 := coherence.NearCacheOptions{HighUnits: 100, PruneFactor: 0.2}
3638

3739
testCases := []struct {
3840
testName string
@@ -55,6 +57,8 @@ func TestNearCacheOperationsAgainstMapAndCache(t *testing.T) {
5557
{"RunTestNearCacheGetAllNamedCache2", GetNearCacheNamedCache[int, utils.Person](g, session, "near-cache-get-all-cache2", coherence.WithNearCache(&nearCacheOptions120Seconds)), RunTestNearCacheGetAll2},
5658
{"RunTestNearCacheWithHighUnitsNamedMap", GetNearCacheNamedMap[int, utils.Person](g, session, "near-cache-high-units-map", coherence.WithNearCache(&nearCacheOptionsHighUnits1)), RunTestNearCacheWithHighUnits},
5759
{"RunTestNearCacheWithHighUnitsNamedCache", GetNearCacheNamedCache[int, utils.Person](g, session, "near-cache-high-units-cache", coherence.WithNearCache(&nearCacheOptionsHighUnits1)), RunTestNearCacheWithHighUnits},
60+
{"RunTestNearCacheWithHighUnitsNamedMapPruneFactor", GetNearCacheNamedMap[int, utils.Person](g, session, "near-cache-high-units-map", coherence.WithNearCache(&nearCacheOptionsHighUnits3)), RunTestNearCacheWithHighUnits},
61+
{"RunTestNearCacheWithHighUnitsNamedCachePruneFactor", GetNearCacheNamedCache[int, utils.Person](g, session, "near-cache-high-units-cache", coherence.WithNearCache(&nearCacheOptionsHighUnits3)), RunTestNearCacheWithHighUnits},
5862
{"RunTestNearCacheWithHighUnitsMemoryNamedMap", GetNearCacheNamedMap[int, utils.Person](g, session, "near-cache-high-units-mem-map", coherence.WithNearCache(&nearCacheOptionsHighUnits2)), RunTestNearCacheWithHighUnitsMemory},
5963
{"RunTestNearCacheWithHighUnitsMemoryNamedCache", GetNearCacheNamedCache[int, utils.Person](g, session, "near-cache-high-units-mem-cache", coherence.WithNearCache(&nearCacheOptionsHighUnits2)), RunTestNearCacheWithHighUnitsMemory},
6064
{"RunTestNearCacheWithHighUnitsAccessNamedMap", GetNearCacheNamedMap[int, utils.Person](g, session, "near-cache-high-units-access-mem-map", coherence.WithNearCache(&nearCacheOptionsHighUnits1)), RunTestNearCacheWithHighUnitsAccess},
@@ -342,12 +346,16 @@ func RunTestNearCacheWithHighUnits(t *testing.T, namedMap coherence.NamedMap[int
342346
// should have 100 entries in near cache
343347
g.Expect(namedMap.GetNearCacheStats().Size()).To(gomega.Equal(100))
344348

345-
// issue a Get() for an entry not in the cache which will trigger the HighUnits and prune to 80 entries
349+
// issue a Get() for an entry not in the cache which will trigger the HighUnits and prune entries
346350
_, err = namedMap.Get(ctx, 110)
347351
g.Expect(err).ShouldNot(gomega.HaveOccurred())
348352

349-
g.Expect(namedMap.GetNearCacheStats().Size()).To(gomega.Equal(80))
350-
g.Expect(namedMap.GetNearCacheStats().GetCachePrunes()).To(gomega.Equal(int64(1)))
353+
// get the current PuneFactor
354+
pruneFactor := coherence.GetNearCachePruneFactor(namedMap)
355+
expectedSize := int(math.Round(float64(float32(100) * pruneFactor)))
356+
357+
g.Expect(namedMap.GetNearCacheStats().Size()).To(gomega.Equal(expectedSize))
358+
g.Expect(namedMap.GetNearCacheStats().GetCachePrunes()).ToNot(gomega.Equal(int64(0)))
351359
}
352360

353361
// RunTestNearWithClear tests a near cache that issues puts, get then clears..
@@ -438,8 +446,12 @@ func RunTestNearCacheWithHighUnitsAccess(t *testing.T, namedMap coherence.NamedM
438446
_, err = namedMap.Get(ctx, 110)
439447
g.Expect(err).ShouldNot(gomega.HaveOccurred())
440448

441-
g.Expect(namedMap.GetNearCacheStats().Size()).To(gomega.Equal(80))
442-
g.Expect(namedMap.GetNearCacheStats().GetCachePrunes()).To(gomega.Equal(int64(1)))
449+
// get the current PuneFactor
450+
pruneFactor := coherence.GetNearCachePruneFactor(namedMap)
451+
expectedSize := int(math.Round(float64(float32(100) * pruneFactor)))
452+
453+
g.Expect(namedMap.GetNearCacheStats().Size()).To(gomega.Equal(expectedSize))
454+
g.Expect(namedMap.GetNearCacheStats().GetCachePrunes()).ToNot(gomega.Equal(int64(0)))
443455

444456
t.Log("near cache stats before get", namedMap.GetNearCacheStats())
445457

@@ -547,6 +559,30 @@ func TestDuplicateNamedCache(t *testing.T) {
547559
namedMap.Release()
548560
}
549561

562+
// TestNearCachePruneFactor runs tests to ensure we honor the pruneFactor.
563+
func TestNearCachePruneFactor(t *testing.T) {
564+
g := gomega.NewWithT(t)
565+
566+
session, err := utils.GetSession()
567+
g.Expect(err).ShouldNot(gomega.HaveOccurred())
568+
defer session.Close()
569+
570+
// test value of 0.2
571+
nearCacheOptions1 := coherence.NearCacheOptions{HighUnits: 100, PruneFactor: 0.2}
572+
namedCache, err := coherence.GetNamedCache[int, string](session, nearCacheName, coherence.WithNearCache(&nearCacheOptions1))
573+
g.Expect(err).ShouldNot(gomega.HaveOccurred())
574+
575+
// validate the factor
576+
g.Expect(coherence.GetNearCachePruneFactor[int, string](namedCache)).To(gomega.Equal(nearCacheOptions1.PruneFactor))
577+
namedCache.Release()
578+
579+
// test default value of 0.8
580+
nearCacheOptions2 := coherence.NearCacheOptions{HighUnits: 100}
581+
namedCache, err = coherence.GetNamedCache[int, string](session, nearCacheName, coherence.WithNearCache(&nearCacheOptions2))
582+
g.Expect(err).ShouldNot(gomega.HaveOccurred())
583+
g.Expect(coherence.GetNearCachePruneFactor[int, string](namedCache)).To(gomega.Equal(float32(0.8)))
584+
}
585+
550586
// TestInvalidNearCacheOptions runs tests to ensure that we can't create a named cache/map with invalid options.
551587
func TestInvalidNearCacheOptions(t *testing.T) {
552588
var (
@@ -591,6 +627,23 @@ func TestInvalidNearCacheOptions(t *testing.T) {
591627
_, err = coherence.GetNamedMap[int, string](session, nearMapName, coherence.WithNearCache(&nearCacheOptionsBad3))
592628
g.Expect(err).Should(gomega.HaveOccurred())
593629
g.Expect(err).Should(gomega.Equal(coherence.ErrInvalidNearCacheWithNoTTL))
630+
631+
// cannot have Invalid PruneFactor
632+
nearCacheOptionsBad4 := coherence.NearCacheOptions{HighUnits: 1000, PruneFactor: -1.0}
633+
_, err = coherence.GetNamedMap[int, string](session, nearMapName, coherence.WithNearCache(&nearCacheOptionsBad4))
634+
g.Expect(err).Should(gomega.HaveOccurred())
635+
g.Expect(err).Should(gomega.Equal(coherence.ErrInvalidPruneFactor))
636+
637+
nearCacheOptionsBad4.PruneFactor = 0.05
638+
_, err = coherence.GetNamedMap[int, string](session, nearMapName, coherence.WithNearCache(&nearCacheOptionsBad4))
639+
g.Expect(err).Should(gomega.HaveOccurred())
640+
g.Expect(err).Should(gomega.Equal(coherence.ErrInvalidPruneFactor))
641+
642+
nearCacheOptionsBad4.PruneFactor = 1.01
643+
_, err = coherence.GetNamedMap[int, string](session, nearMapName, coherence.WithNearCache(&nearCacheOptionsBad4))
644+
g.Expect(err).Should(gomega.HaveOccurred())
645+
g.Expect(err).Should(gomega.Equal(coherence.ErrInvalidPruneFactor))
646+
594647
}
595648

596649
// TestIncompatibleNearCacheOptions runs tests to ensure that we can't create a named cache/map with incompatible.

0 commit comments

Comments
 (0)