-
Notifications
You must be signed in to change notification settings - Fork 123
TRT-2564: Cache Risk Analysis responses #3409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
edd7a7a
0901e69
d61c643
5161c23
42dd74c
effe5d9
d011d01
4a04ead
ea4b4c1
bdb18a7
0a11e04
c3dcdb4
0b44e19
f8159b5
542cdcb
d2822a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,6 +137,9 @@ func CacheSet[T any](ctx context.Context, c cache.Cache, result T, cacheKey []by | |
| func CalculateRoundedCacheDuration(cacheOptions cache.RequestOptions) time.Duration { | ||
| // require cacheDuration for persistence logic | ||
| cacheDuration := defaultCacheDuration | ||
| if cacheOptions.Expiry > 0 { | ||
| cacheDuration = cacheOptions.Expiry | ||
| } | ||
| if cacheOptions.CRTimeRoundingFactor > 0 { | ||
| now := time.Now().UTC() | ||
| // Only cache until the next rounding duration | ||
|
|
@@ -158,3 +161,80 @@ func isStructWithNoPublicFields(v interface{}) bool { | |
| } | ||
| return true | ||
| } | ||
|
|
||
| // GetDataFromCacheOrMatview caches data that is based on a matview and invalidates it when the matview is refreshed | ||
| func GetDataFromCacheOrMatview[T any](ctx context.Context, | ||
| cacheClient cache.Cache, cacheSpec CacheSpec, | ||
| matview string, | ||
| cacheDuration time.Duration, | ||
| generateFn func(context.Context) (T, []error), | ||
| defaultVal T, | ||
| ) (T, []error) { | ||
| if cacheClient == nil { | ||
| return generateFn(ctx) | ||
| } | ||
|
|
||
| cacheKey, err := cacheSpec.GetCacheKey() | ||
| if err != nil { | ||
| return defaultVal, []error{err} | ||
| } | ||
|
|
||
| // If someone gives us an uncacheable cacheKey, panic so it gets detected in testing | ||
| if len(cacheKey) == 0 { | ||
| panic(fmt.Sprintf("cache key is empty for %s", reflect.TypeOf(defaultVal))) | ||
| } | ||
| // If someone gives us an uncacheable value, panic so it gets detected in testing | ||
| if isStructWithNoPublicFields(defaultVal) { | ||
| panic(fmt.Sprintf("cannot cache type %s that exports no fields", reflect.TypeOf(defaultVal))) | ||
| } | ||
|
|
||
| var cacheVal struct { | ||
| Val T // the actual value we want to cache | ||
| Timestamp time.Time // the time when it was cached (for comparison to matview refresh time) | ||
| } | ||
| if cached, err := cacheClient.Get(ctx, string(cacheKey), 0); err == nil { | ||
| logrus.WithFields(logrus.Fields{ | ||
| "key": string(cacheKey), | ||
| "type": reflect.TypeOf(defaultVal).String(), | ||
| }).Debugf("cache hit") | ||
|
|
||
| if err := json.Unmarshal(cached, &cacheVal); err != nil { | ||
| logrus.WithError(err).Warnf("failed to unmarshal cached item. cacheKey=%+v", cacheKey) | ||
| // fall through to generate the data instead | ||
| } else { | ||
| // look up when the matview was refreshed to see if the cached value is stale | ||
| var lastRefresh time.Time | ||
| if lastRefreshBytes, err := cacheClient.Get(ctx, RefreshMatviewKey(matview), 0); err == nil { | ||
| if parsed, err := time.Parse(time.RFC3339, string(lastRefreshBytes)); err != nil { | ||
| logrus.WithError(err).Warnf("failed to parse matview refresh timestamp %q for %q; cache will not be invalidated", lastRefreshBytes, matview) | ||
| } else { | ||
| lastRefresh = parsed | ||
| } | ||
| } | ||
|
|
||
| if lastRefresh.Before(cacheVal.Timestamp) { | ||
| // not invalidated by a newer refresh, so use it (if we don't know the last refresh, still use it) | ||
| return cacheVal.Val, nil | ||
| } | ||
| logrus.Debugf("matview %q refreshed at %v, will not use earlier cache entry from %v", matview, lastRefresh, cacheVal.Timestamp) | ||
| } | ||
| } else if strings.Contains(err.Error(), "connection refused") { | ||
| logrus.WithError(err).Fatalf("redis URL specified but got connection refused; exiting due to cost issues in this configuration") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
could be a networking blip but I guess we can robustify if we run into this too often
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i do not know the history of why it was done this way and it did not seem likely to be a common problem that a blip would kill the server (but maybe it's been happening all the time and we just didn't notice because they're deployed redundantly) |
||
| } else { | ||
| logrus.WithFields(logrus.Fields{"key": string(cacheKey)}).Debugf("cache miss") | ||
| } | ||
|
|
||
| // Cache missed or refresh invalidated the data, so generate it. | ||
| logrus.Debugf("cache duration set to %s or approx %s for key %s", cacheDuration, time.Now().Add(cacheDuration).Format(time.RFC3339), cacheKey) | ||
| result, errs := generateFn(ctx) | ||
| if len(errs) == 0 { | ||
| cacheVal.Val = result | ||
| cacheVal.Timestamp = time.Now().UTC() | ||
| CacheSet(ctx, cacheClient, cacheVal, cacheKey, cacheDuration) | ||
| } | ||
| return result, errs | ||
| } | ||
|
|
||
| func RefreshMatviewKey(matview string) string { | ||
| return "matview_refreshed:" + matview | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.