-
Notifications
You must be signed in to change notification settings - Fork 859
1178-MCV-BE Initial lazySetCache implementation #4950
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
1178-MCV-BE Initial lazySetCache implementation #4950
Conversation
Size Change: -10 B (0%) Total Size: 6.87 MB ℹ️ View Unchanged
|
server/src/lib/lazy-set-cache.ts
Outdated
const pipeline = redis.multi() | ||
pipeline.del(key) | ||
if (values.length > 0) { | ||
pipeline.sadd(key, ...values.map(String)) | ||
} | ||
pipeline.expire(key, msToSeconds(cacheDurationMs)) | ||
await pipeline.exec() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const pipeline = redis.multi() | |
pipeline.del(key) | |
if (values.length > 0) { | |
pipeline.sadd(key, ...values.map(String)) | |
} | |
pipeline.expire(key, msToSeconds(cacheDurationMs)) | |
await pipeline.exec() | |
if (values.length === 0) return | |
const pipeline = redis.multi() | |
pipeline.del(key) | |
pipeline.sadd(key, ...values.map(String)) | |
pipeline.expire(key, msToSeconds(cacheDurationMs)) | |
await pipeline.exec() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the guard and bad expire reset.
Let us figure out the following case which I was trying to handle - it seems my logic was flawed:
- We expose this and some logic in upper layer decides to forced cache re-fill through this before expiry (e.g. other user's actions changed the DB state, and this user's "unvalidated-sentence-id:client-id" must be refilled forgetting old ones.
- But there are none found (values.length === 0)
- From the previous cache refill we have cache members (until expiry) and if the cache is directly used (regular lazySetCache call) they will still be presented to the user (e.g. causing unnecessary voting in the example).
I think we still need to set the cache to an empty state here - but then after a lazySetCache call, because it is empty, it will again try to refill it - and again it will be empty... That would cause a loop and unnecessary DB access (sometimes costly).
One solution would be NOT to expose this function and only use it through lazySetCache
and control the whole "empty cache" state there, e.g. by checking the expire and not try to refill until it expires (currently it tries to refill on each call, e.g. after page refresh).
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I think the original code is still safer. When the application or main function decides to "fill" and the result set it empty:
- Old values will be reset to empty set (forgotten)
- We will have a new expiry
Now, assume a case with cache-duration = 10 min, lock-duration = 1 min. That would result in 8 additional DB hits...
- This can be considered as a good thing, in the meantime new ones can become valid and begin to be served - but if nothing is happening on DB, the DB hits will be in vain.
- We can be adaptive (which I was thinking for adaptive-cache) - We can start with 1 min lock, if still locked double the lock duration to 2, then 4, 8 - up to max = cache-duration (for idle on FE - e.g. nobody adding sentences, but keep trying validate). This would require "key" + "key-lock-dur" (int)...
What do you think?
I propose let's keep it as it is (I converted it to a class) and deal with it later when actually connecting them. This implementation has no connections for now...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably remove the lazySetCache
prefix from the functions as you're importing the functions from the lazy-set-cache
module/file. The caller can either import all or some functions, e.g.
import * as LazySetCache from './lazy-set-cache.ts'
import { fill } from './lazy-set-cache.ts'
LazySetCache.addWithExpiry('key', 'cool', TimeUnits.DAY)
fill('key', ['cool'], TimeUnits.DAY)
// Compared to
// LazySetCache.lazySetCacheAddWithExpiry('key', 'cool', TimeUnits.DAY)
The user importing the lazy-set-cache
file should understand that the module deals with sets, there shouldn't be a need to explicitly add the prefix to every function. If users are just importing some function, it can become hard to understand, e.g. fill('key', ['cool'], TimeUnits.DAY)
is not really clear what that is. In this case, you could use
export const LazySetCache = {
async function fill(
key: string,
values: (number | string)[],
cacheDurationMs: number
): Promise<void> {...}
}
so that others have to import import { LazySetCache } from './lazy-set-cache.ts'
and call LazySetCache.fill()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { LazySetCache } from './lazy-set-cache.ts'
LazySetCache.fill()
This method will be most useful , thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted to a class with methods...
Internal issue
Internal main issue
The Redis based lazy cache implementation for sets (numbers and strings), more performant and exposes more functionality.