Skip to content

Better memory limit support #143

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
Suor opened this issue Apr 28, 2015 · 17 comments
Closed

Better memory limit support #143

Suor opened this issue Apr 28, 2015 · 17 comments

Comments

@Suor
Copy link
Owner

Suor commented Apr 28, 2015

For now cacheops offers 2 imperfect strategies to handle that. They both have flaws. I create this issue to track the topic.

@Suor
Copy link
Owner Author

Suor commented Apr 28, 2015

Alternative strategies available now:

  1. Switch off maxmemory. Use external periodic job to make custom cleanup when memory usage exceeds limit. Cons: clunky, lag before eviction can cause arbitrary memory use.
  2. Use keyspace notifications and external daemon to subscribe and manage cache structure. Cons: clunky, can miss events upon disconnect, is async so eviction could delete more than needed.
  3. Store set of conj keys in a cache key and check integrity on cache fetch. Cons: slower fetch, substantial code complication.

@Suor
Copy link
Owner Author

Suor commented Apr 28, 2015

The ideal solution would be custom eviction strategy, probably lua-based - https://github.com/antirez/redis/pull/2319. Another good solution could be managing cache structure with lua script subscribed to keyspace notification - https://github.com/antirez/redis/issues/2540.

@gposton
Copy link

gposton commented May 17, 2016

@Suor, what are the chances that you can provide a script (or guidance on what the script needs to do) for option 1. We need to put something in place until cacheops supports a solid solution natively (hopefully option 2 or 3).

@Suor
Copy link
Owner Author

Suor commented May 18, 2016

I won't provide a script, but I can elaborate on strategy:

  • use INFO MEMORY command to find out if usage is above limit,
  • select some keys with RANDOMKEY, choose conj:* from them,
  • for each conjuction key, select its members and delete those keys with conjunction key itself:
keys = redis_client.smembers(conj_key)
redis_client.delete(*([conj_key] + keys))

(last one is better to run in Lua for atomicity)

@Suor
Copy link
Owner Author

Suor commented May 18, 2016

The alternative, let's call it strategy 1a is probably better:

  • use CACHEOPS_LRU = True and maxmemory-policy volatile-lru (second strategy from README)
  • periodically SCAN for conjuction keys and remove them if they are orphant:
for conj_key in redis_client.scan_iter(match='conj:*'):
    keys = redis_client.smembers(conj_key)
    exists = redis_client.execute_command('EXISTS', *keys)
    if exists = 0:
        redis_client.delete(conj_key)

(the innards of the loop should be done with Lua for atomicity)

Edit: maxmemory-policy volatile-ttl changed to volcatile-lru, which one used by second README strategy.

@caseydm
Copy link

caseydm commented Jan 17, 2018

Hi all. I'm trying to understand what it means to use the eviction policy recommended in the README, which is CACHEOPS_LRU = True and maxmemory-policy volatile-lru. If I run my cache like this, do I lose the ability to expire cached views based on time? Is the only way to remove an item from the cache to let it get 'pushed out' by newer items?

What I want is to have my view cache expire after 24 hours like normal, BUT if I hit the max memory limit, the oldest items are pushed out to make room for the new ones.

@Suor
Copy link
Owner Author

Suor commented Jan 17, 2018

No you don't loose ability to expire by time. The only downside is that invalidation structures can clutter your redis db over time, cache keys are still evicted by timeout.

@caseydm
Copy link

caseydm commented Jan 17, 2018

Oh ok. So if I understand this right, two keys are created for each item that is cached, one is the actual content and the other is the invalidation instructions. With the method I mentioned the content keys will be removed, but the invalidate keys will remain? And if I run the conj_key function as a management command every so often those invalidation keys will be cleared out?

@Suor
Copy link
Owner Author

Suor commented Jan 18, 2018

Several conj_keys refer to single cache key, here is the description of how it works. When you use CACHEOPS_LRU = True conj_keys are not evicted by time, so they may clutter up, referencing non-existing cache keys. They are still removed on corresponding events so this might be not an issue.

There is no such thing as conj_key function. You basically need to go through conj_keys and check if they refer only non-existing cache keys and remove them if they are, I wrote the draft above. It could be improved though - remove non-existing cache keys from conj key instead of checking all of them and removing the whole set only:

for conj_key in r.scan_iter(match='conj:*'):
    for cache_key in r.smembers(conj_key):
        # These two lines should be done atomically
        if not r.exists(cache_key):
            r.srem(conj_key, cache_key) 

Redis automatically removes keys for empty sets, so that's it.

@caseydm
Copy link

caseydm commented Jan 19, 2018

Thank you for the quick reply. I’m trying out this strategy and will report back.

@wolph
Copy link

wolph commented Apr 17, 2019

I know it would take a large amount of effort to do right, but I think it would be beneficial if we could configure multiple cache backends. That would make this memory limit issue also easily solvable by running multiple redis instances (which many people do already since redis is single-cpu).

@Suor
Copy link
Owner Author

Suor commented Apr 18, 2019

This has nothing to do with other backends. BTW cacheops doesn't use other backends because it uses sets and set operations in redis, which other backends just don't provide.

@wolph
Copy link

wolph commented Apr 18, 2019

You misunderstood. I'm talking about multiple redis servers so you can have memory limits through redis.

@Suor
Copy link
Owner Author

Suor commented Apr 18, 2019

Multiple redises have nothing to do with memory limit.

@wolph
Copy link

wolph commented Apr 18, 2019

I don't see why not? Youl could have multiple redis servers and you can specify the maxmemory per server separately.

For example, assuming you have your sessions in redis you want to be absolutely certain they will never reach an out-of-memory scenario. Whereas many cache layers don't haver any real priority so you can set that server to allkeys-lru so you omit the need for a setex or expire.

@Suor
Copy link
Owner Author

Suor commented Apr 18, 2019

All this doesn't matter from cacheops implementation point of view multiple server support and memory limit are completely independent issues. There is no reason to talk about multiple servers or backends here.

Suor added a commit that referenced this issue Feb 25, 2023
The idea is that instead of saving all dependent cache keys in conj set
we put a simple random stamp in those and store a checksum of all
related conj stamps along with cache data.

This makes cache reads more complicated - `MGET` key + conj keys,
validate stamps checksum. However, we no longer need to store
potentially big conj sets and invalidation becomes faster, including
model level invalidation.

It also removes strong link between conj and cache keys, i.e. loss of
conj keys no longer leads to a stale cache, instead we will simply drop
the key on next read. This opens easier way for maxmemory and cluster.

So:
- more friendly to `maxmemory`, even assumes that, see #143
- eliminates issues with big conj sets and long invalidation, see #340,
- `reapconjs` is not needed with it, see #323, #434

Followups:
- docs
- remove `CACHEOPS_LRU` as it's superseeded by this generally
- make insideout default or even drop the old ways?
Suor added a commit that referenced this issue Feb 25, 2023
The idea is that instead of saving all dependent cache keys in conj set
we put a simple random stamp in those and store a checksum of all
related conj stamps along with cache data.

This makes cache reads more complicated - `MGET` key + conj keys,
validate stamps checksum. However, we no longer need to store
potentially big conj sets and invalidation becomes faster, including
model level invalidation.

It also removes strong link between conj and cache keys, i.e. loss of
conj keys no longer leads to a stale cache, instead we will simply drop
the key on next read. This opens easier way for maxmemory and cluster.

So:
- more friendly to `maxmemory`, even assumes that, see #143
- eliminates issues with big conj sets and long invalidation, see #340,
- `reapconjs` is not needed with it, see #323, #434

Followups:
- docs
- remove `CACHEOPS_LRU` as it's superseeded by this generally
- make insideout default or even drop the old ways?
Suor added a commit that referenced this issue Feb 25, 2023
The idea is that instead of saving all dependent cache keys in conj set
we put a simple random stamp in those and store a checksum of all
related conj stamps along with cache data.

This makes cache reads more complicated - `MGET` key + conj keys,
validate stamps checksum. However, we no longer need to store
potentially big conj sets and invalidation becomes faster, including
model level invalidation.

It also removes strong link between conj and cache keys, i.e. loss of
conj keys no longer leads to a stale cache, instead we will simply drop
the key on next read. This opens easier way for maxmemory and cluster.

So:
- more friendly to `maxmemory`, even assumes that, see #143
- eliminates issues with big conj sets and long invalidation, see #340,
  #350, #444
- `reapconjs` is not needed with it, see #323, #434

Followups:
- docs
- remove `CACHEOPS_LRU` as it's superseeded by this generally
- make insideout default or even drop the old ways?
@Suor
Copy link
Owner Author

Suor commented Feb 25, 2023

Using CACHEOPS_INSIDEOUT = True is a blessed way to solve this now, see Using memory limit

@Suor Suor closed this as completed Feb 25, 2023
# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

4 participants