Skip to content

Commit

Permalink
Improve strict coalescing doc with examples resolves #4
Browse files Browse the repository at this point in the history
  • Loading branch information
motoki317 committed Nov 5, 2023
1 parent f2c377b commit 703f9e6
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 17 deletions.
14 changes: 7 additions & 7 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func TestNew(t *testing.T) {
})
}

// TestCache_Get calls Cache.Get multiple times and ensures a value is reused.
// TestCache_Get calls (*Cache).Get multiple times and ensures a value is reused.
func TestCache_Get(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -268,7 +268,7 @@ func TestCache_Get(t *testing.T) {
}
}

// TestCache_Get_Async ensures that Cache.Get will trigger background fetch if a stale value is found.
// TestCache_Get_Async ensures that (*Cache).Get will trigger background fetch if a stale value is found.
func TestCache_Get_Async(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -324,7 +324,7 @@ func TestCache_Get_Async(t *testing.T) {
}
}

// TestCache_Get_Error ensures Cache.Get returns an error if replaceFn returns an error.
// TestCache_Get_Error ensures (*Cache).Get returns an error if replaceFn returns an error.
func TestCache_Get_Error(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -433,7 +433,7 @@ func TestCache_GetIfExists(t *testing.T) {
}
}

// TestCache_Notify tests that Cache.Notify will replace the value in background.
// TestCache_Notify tests that (*Cache).Notify will replace the value in background.
func TestCache_Notify(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -486,7 +486,7 @@ func TestCache_Notify(t *testing.T) {
}
}

// TestCache_Forget_Interrupt ensures that calling Cache.Forget will make later Get calls trigger replaceFn.
// TestCache_Forget_Interrupt ensures that calling (*Cache).Forget will make later Get calls trigger replaceFn.
func TestCache_Forget_Interrupt(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -580,7 +580,7 @@ func TestCache_Forget_NoInterrupt(t *testing.T) {
}
}

// TestCache_ForgetIf ensures that calling Cache.ForgetIf will make later Get calls trigger replaceFn.
// TestCache_ForgetIf ensures that calling (*Cache).ForgetIf will make later Get calls trigger replaceFn.
func TestCache_ForgetIf(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -806,7 +806,7 @@ func TestCache_ParallelReplacement(t *testing.T) {
}
}

// TestCache_MultipleValues calls Cache.Get with some different keys, and ensures correct values are returned.
// TestCache_MultipleValues calls (*Cache).Get with some different keys, and ensures correct values are returned.
func TestCache_MultipleValues(t *testing.T) {
t.Parallel()

Expand Down
65 changes: 59 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,70 @@ func With2QBackend(capacity int) CacheOption {
}
}

// EnableStrictCoalescing enables strict coalescing check with a slight overhead. The check prevents Get calls
// coming later in time to be coalesced with already stale response generated by a Get call earlier in time.
// This is similar to the behavior of 'automatically calling' Cache.Forget after a value is expired, but different in
// that it does not initiate a new request until the current one finishes or Cache.Forget is called.
// EnableStrictCoalescing enables 'strict coalescing check' with a slight overhead. The check prevents Get() calls
// coming later in time to be coalesced with already stale response generated by a Get() call earlier in time.
//
// Ordinary cache users should not need this behavior.
//
// This is similar to 'automatically calling' (*Cache).Forget after a value is expired, but different in that
// it does not allow initiating new request until the current one finishes or (*Cache).Forget is explicitly called.
//
// Using this option, one may construct a 'throttler' / 'coalescer' not only of get requests but also of update requests.
//
// This is a generalization of so-called 'zero-time-cache', where the original zero-time-cache behavior is
// achievable with zero freshFor/ttl values.
// see also: https://qiita.com/methane/items/27ccaee5b989fb5fca72 (ja)
//
// This is only useful if you need to make sure that no Get calls are served expired values due to coalescing.
// Most users should not need this behavior.
// ## Example with freshFor == 0 and ttl == 0
//
// 1st Get() call will return value from the first replaceFn call.
//
// 2nd Get() call will NOT return value from the first replaceFn call, since by the time 2nd Get() call is made,
// value from the first replaceFn call is already considered expired.
// Instead, 2nd Get() call will initiate the second replaceFn call, and will return that value.
// Without EnableStrictCoalescing option, 2nd Get() call will share the value from the first replaceFn call.
//
// In order to immediately initiate next replaceFn call without waiting for the previous replaceFn call to finish,
// use (*Cache).Forget or (*Cache).Purge.
//
// Similarly, 3rd and 4th Get() call will NOT return value from the second replaceFn call, but instead initiate
// the third replaceFn call.
//
// With EnableStrictCoalescing
//
// Get() is called....: 1 2 3 4
// returned value.....: 1 2 3 3
// replaceFn is called: 1---->12---->23---->3
//
// Without EnableStrictCoalescing
//
// Get() is called....: 1 2 3 4
// returned value.....: 1 1 2 2
// replaceFn is called: 1---->1 2---->2
//
// ## Example with freshFor == 1s and ttl == 1s
//
// 1st, 2nd, and 3rd Get() calls all return value from the first replaceFn call, since the value is considered fresh.
//
// 4th and 5th call do NOT return value from the first replaceFn call, since by the time these calls are made,
// value by the first replaceFn call is already considered expired.
// Instead, 4th (and 5th) call will initiate the second replaceFn call.
// Without EnableStrictCoalescing option, 4th call will share the value from the first replaceFn call,
// and 5th Get() call will initiate the second replaceFn call.
//
// With EnableStrictCoalescing:
//
// Elapsed time (s)...: 0 1 2
// Get() is called....: 1 2 3 4 5
// returned value.....: 1 1 1 2 2
// replaceFn is called: 1------------>12------------>2
//
// Without EnableStrictCoalescing:
//
// Elapsed time (s)...: 0 1 2
// Get() is called....: 1 2 3 4 5
// returned value.....: 1 1 1 1 2
// replaceFn is called: 1------------>1 2------------>2
func EnableStrictCoalescing() CacheOption {
return func(c *cacheConfig) {
c.enableStrictCoalescing = true
Expand Down
9 changes: 5 additions & 4 deletions stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
// Stats represents cache metrics.
//
// Cache hit ratio can be calculated as:
// (cache hit ratio) = (Hits + GraceHits) / (Hits + GraceHits + Misses)
//
// (cache hit ratio) = (Hits + GraceHits) / (Hits + GraceHits + Misses)
type Stats struct {
// Hits is the number of fresh cache hits in Cache.Get.
// Hits is the number of fresh cache hits in (*Cache).Get.
Hits uint64
// GraceHits is the number of stale cache hits in Cache.Get.
// GraceHits is the number of stale cache hits in (*Cache).Get.
GraceHits uint64
// Misses is the number of cache misses in Cache.Get.
// Misses is the number of cache misses in (*Cache).Get.
Misses uint64
// Replacements is the number of times replaceFn is called.
// Note that this field is incremented after replaceFn finishes to reduce lock time.
Expand Down

0 comments on commit 703f9e6

Please sign in to comment.