Opened 20 months ago

Last modified 3 weeks ago

#20892 new New feature

add maximum item size configuration to memcached cache backend

Reported by: alex@… Owned by: nobody
Component: Core (Cache system) Version: 1.5
Severity: Normal Keywords:
Cc: alex@…, shai@…, olavmrk@… Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

This appears to be an issue related to the fact that older releases of memcached insisted on a 1 MB value size limit.

Current releases of memcached have a user-configurable limit, however Django still uses a hard-wired 1 MB limit.

This (undocumented) 'feature' is currently causing errors, see the following two SO questions (one by me):

http://stackoverflow.com/questions/18143159/apparent-bug-storing-large-keys-in-django-memcached
http://stackoverflow.com/questions/11874377/django-caching-a-large-list

A 'solution' is proposed by the second SO question:

also edit this class in memcached.py located in /usr/lib/python2.7/dist-packages/django/core/cache/backends to look like this:

class MemcachedCache(BaseMemcachedCache):
"An implementation of a cache binding using python-memcached"
def __init__(self, server, params):
    import memcache
    memcache.SERVER_MAX_VALUE_LENGTH = 1024*1024*10 #added limit to accept 10mb
    super(MemcachedCache, self).__init__(server, params,
                                         library=memcache,
                                         value_not_found_exception=ValueError)

Perhaps this could be added to the config of the logger?

For example, an ITEM_SIZE_MAX key could be created for the dictionary, so the following code could be used:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'ITEM_SIZE_MAX': 10,
        'LOCATION': 'localhost:11211',
    }
}

Obviously, the user would also have to add the necessary -I parameter to their /etc/memcached.conf (system dependent). I suppose the best would be if we could automatically detect the item size from their current memcached setup. For example:

from subprocess import Popen, Pipe
memcached_call = Popen("echo stats settings | nc localhost 11211", stdout=PIPE, stderr=PIPE, shell=True)
out, err = memcached_call.communicate()
if err:
    log("Error when getting memcached stats to find item size max: "+err+"\n. Assuming max size is 1 MB")
    maxsize_int = 1024*1024
else:
    values = out.split("\n")
    maxsize_field = filter(lambda field: field.startswith("STAT item_size_max"), values)[0]
    maxsize_int = int(maxsize_field.strip()[maxsize_field.rindex(" "):])

Change History (11)

comment:1 Changed 20 months ago by anonymous

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
import memcache
memcache.SERVER_MAX_VALUE_LENGTH = 1024*1024*10 #added limit to accept 10mb

Apparently this solution does NOT actually work... the value from the module is not propagated to the client properly.
I've reported the issue to python-memcache: https://github.com/linsomniac/python-memcached/issues/13

comment:2 Changed 20 months ago by aaugustin

As far as I can tell, Django itself doesn't contain any code to limit the size of cached items. (If it does, sorry, I missed it.)

Could you clarify your assertion that "Django still uses a hard-wired 1 MB limit."?

If the limitation is entirely handled by the memcache client library and server, I'm inclined to close this ticket as invalid.

comment:3 Changed 20 months ago by shai

  • Cc shai@… added

If I understand correctly, the ticket complains that there is no sane way to configure the max-item-size for use in Django; this leads to Django apps "always" using the default 1M limit. For the suggested change to work, the user also needs to take care of their server (at least if using a larger value), and if the anonymous user is right, also fix a bug in the client lib, but the Django part is missing either way.

I think that is valid.

comment:4 Changed 20 months ago by timo

It's possible to accomplish this using a custom cache backend, but it may be nice to make this a bit easier:

import pickle

from django.core.cache.backends.memcached import MemcachedCache

MIN_COMPRESS_LENGTH = 1048576 # 1MB

class CustomMemcachedCache(MemcachedCache):

    @property
    def _cache(self):
        """
        Override to add pass some custom parameters to the python_memcached
        backend.
        """
        if getattr(self, '_client', None) is None:
            options = {}
            if self._options and 'SERVER_MAX_VALUE_LENGTH' in self._options:
                options['server_max_value_length'] = self._options['SERVER_MAX_VALUE_LENGTH']
            self._client = self._lib.Client(self._servers,
                pickleProtocol = pickle.HIGHEST_PROTOCOL, **options
            )
        return self._client

    def set(self, key, value, timeout=0, version=None):
        """
        Override to pass MIN_COMPRESS_LENGTH so large values are compressed.
        """
        key = self.make_key(key, version=version)
        self._cache.set(key, value, self._get_memcache_timeout(timeout), MIN_COMPRESS_LENGTH)

comment:5 Changed 20 months ago by anonymous

""If I understand correctly, the ticket complains that there is no sane way to configure the max-item-size for use in Django""

Precisely.

Django does not deliberately limit the size, but it's dependent modules do, without having their configuration options passed through.

comment:6 Changed 20 months ago by anonymous

Sorry guys, I just realised I really ought to make an account and stop posting anonymously... I posted this issue, and the anonymous comments are by me.

comment:7 Changed 20 months ago by AlexF124

Ok, python-memcache has gone ahead and fixed the bug, so the

import memcache
memcache.SERVER_MAX_VALUE_LENGTH = 1024*1024*10 #added limit to accept 10mb

would work from python-memcache's next release and onwards.

Last edited 20 months ago by AlexF124 (previous) (diff)

comment:8 Changed 20 months ago by aaugustin

I'm still unconvinced there's much value in channeling this configuration through Django's CACHES setting, but I'm not opposing it if another core dev wants it.

Last edited 20 months ago by aaugustin (previous) (diff)

comment:9 Changed 20 months ago by shai

  • Summary changed from Django memcached binding artificially limits item size to add maximum item size configuration to memcached cache backend
  • Triage Stage changed from Unreviewed to Accepted
  • Type changed from Bug to New feature

I'm not sure if that was Aymeric's point, but in any case, there should be a "don't do that unless you know what you're doing" warning on the use of this feature; memcached recommends strongly against it. I think as part of this, the entry should be named "FORCE_ITEM_MAX_SIZE" or something like it.

BTW, I think the most common cause for wanting it -- the only case I've seen up close -- is pushing big objects into a cached session. Perhaps this is something we should specifically recommend against.

comment:10 Changed 19 months ago by timo

  • Component changed from Uncategorized to Core (Cache system)

comment:11 Changed 3 weeks ago by olavmrk

  • Cc olavmrk@… added

Hi,

I ran into this issue now, and have a few comments:

  • This problem only affects the django.core.cache.backends.memcached.MemcachedCache backend. A simple workaround may be to use the django.core.cache.backends.memcached.PyLibMCCache backend.
  • To fix this, one doesn't actually need to write to memcache.SERVER_MAX_VALUE_LENGTH variable. It can also be passed as an option to the constructor: [...].Client(self._servers, pickleProtocol=pickle.HIGHEST_PROTOCOL, server_max_value_length=max_value_size)

As for reasons to want this: In my case, I am fetching a blob of data from a remote web service that is 1.2 MiB. Caching that data blob saves an expensive API call.

Note: See TracTickets for help on using tickets.
Back to Top