mysociety / mapit

A web service to map postcodes to administrative boundaries and more
Other
269 stars 88 forks source link

Replace MemcachedCache backend #390

Closed sagepe closed 1 year ago

sagepe commented 2 years ago

The MemcachedCache cache back-end that relies on python-memcached is deprecated as-of Django 3.2 and needs to be replaced with either PyMemcacheCache (via pymemcache) or PyLibMCCache (via pylibmc). Also, should we consider adding the necessary bindings dependency to setup.py?

See the Django documentation on caching with memcached (3.2, 4.0). Do we want to consider alternatives to memcache, such as Redis which was added in 4.0?

sagepe commented 2 years ago

Noting that since this warning was introduced in Python 3.8, we see the following two syntax warnings from python-memcached:

memcache.py:1303: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if key is '':
memcache.py:1304: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if key_extra_len is 0:

I note that there is a PR with a fix for this, but as python-memcached doesn't seem to be actively maintained at present it hasn't been merged.

davea commented 2 years ago

Just to note that there appears to be some instability when using pymemcache and Django in certain situations: https://code.djangoproject.com/ticket/33092

I did switch to pymemcache for Labour MapIt but it started generating lots of errors similar to those in the Django ticket. Switching back to python-memcache got rid of them.

An example of one such error email: ``` Internal Server Error: /area/10961.geojson AttributeError at /area/10961.geojson 'NoneType' object has no attribute 'recv' Request Method: GET Request URL: http://labour-mapit.societyworks.org/area/10961.geojson?simplify_tolerance=0.0001 Django Version: 3.2.14 Python Executable: /data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/bin/python Python Version: 3.9.2 Python Path: ['/data/vhost/labour-mapit.societyworks.org/labour-mapit-2022-07-15T13-07-05', '/data/vhost/labour-mapit.societyworks.org/labour-mapit', '/usr/lib/python39.zip', '/usr/lib/python3.9', '/usr/lib/python3.9/lib-dynload', '/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages'] Server time: Fri, 15 Jul 2022 14:09:53 +0100 Installed Applications: ['django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.sessions', 'django.contrib.admin', 'django.contrib.gis', 'django.contrib.staticfiles', 'mapit_labour', 'mapit_gb', 'mapit', 'django_q', 'crispy_forms', 'crispy_forms_gds'] Installed Middleware: ['django.middleware.http.ConditionalGetMiddleware', 'django.middleware.cache.UpdateCacheMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'mapit_labour.middleware.LoginOrAPIKeyRequiredMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', 'mapit.middleware.JSONPMiddleware', 'mapit.middleware.ViewExceptionMiddleware'] Traceback (most recent call last): File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/django/utils/deprecation.py", line 119, in __call__ response = self.process_response(request, response) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/django/middleware/cache.py", line 115, in process_response self.cache.set(cache_key, response, timeout) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/django/core/cache/backends/memcached.py", line 82, in set if not self._cache.set(key, value, self.get_backend_timeout(timeout)): File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/hash.py", line 358, in set return self._run_cmd('set', key, False, *args, **kwargs) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/hash.py", line 334, in _run_cmd return self._safely_run_func( File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/hash.py", line 214, in _safely_run_func result = func(*args, **kwargs) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/base.py", line 462, in set return self._store_cmd(b'set', {key: value}, expire, noreply, File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/base.py", line 1103, in _store_cmd buf, line = _readline(self.sock, buf) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/base.py", line 1438, in _readline buf = _recv(sock, RECV_SIZE) File "/data/vhost/labour-mapit.societyworks.org/labour-mapit/.venv/lib/python3.9/site-packages/pymemcache/client/base.py", line 1492, in _recv return sock.recv(size) Exception Type: AttributeError at /area/10961.geojson Exception Value: 'NoneType' object has no attribute 'recv' Request information: USER: davea GET: simplify_tolerance = '0.0001' POST: No POST data FILES: No FILES data COOKIES: csrftoken = 'ltn7HXbPZKgmfaGIkvFsxSQn6NcoW3Q6CgjhgKXjY9XZuIDU8d98iyUlho1iDwpK' sessionid = 'lcvqq052f9jcvpvpq3v4h0g6fldx1cj0' META: CONTENT_TYPE = 'application/x-www-form-urlencoded' CONTEXT_DOCUMENT_ROOT = '/data/vhost/labour-mapit.societyworks.org/docs' CONTEXT_PREFIX = '' DOCUMENT_ROOT = '/data/vhost/labour-mapit.societyworks.org/docs' GATEWAY_INTERFACE = 'CGI/1.1' HTTP_ACCEPT = 'application/json, text/javascript' HTTP_ACCEPT_ENCODING = 'gzip' HTTP_ACCEPT_LANGUAGE = 'en-GB,en;q=0.9' HTTP_COOKIE = 'csrftoken=ltn7HXbPZKgmfaGIkvFsxSQn6NcoW3Q6CgjhgKXjY9XZuIDU8d98iyUlho1iDwpK; sessionid=lcvqq052f9jcvpvpq3v4h0g6fldx1cj0' HTTP_HOST = 'labour-mapit.societyworks.org' HTTP_REFERER = 'https://labour-mapit.societyworks.org/area/1678/children.map.html' HTTP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15' HTTP_X_FORWARDED_PROTO = 'https' HTTP_X_REAL_IP = '217.169.20.84' HTTP_X_REQUESTED_WITH = 'XMLHttpRequest' HTTP_X_VARNISH = '4654373' PATH_INFO = '/area/10961.geojson' PATH_TRANSLATED = '/data/vhost/labour-mapit.societyworks.org/labour-mapit/labour_project/wsgi.py/area/10961.geojson' QUERY_STRING = 'simplify_tolerance=0.0001' REMOTE_ADDR = '217.169.20.84' REMOTE_PORT = '46020' REQUEST_METHOD = 'GET' REQUEST_SCHEME = 'http' REQUEST_URI = '/area/10961.geojson?simplify_tolerance=0.0001' SCRIPT_FILENAME = '/data/vhost/labour-mapit.societyworks.org/labour-mapit/labour_project/wsgi.py' SCRIPT_NAME = '' SCRIPT_URI = 'http://labour-mapit.societyworks.org/area/10961.geojson' SCRIPT_URL = '/area/10961.geojson' SERVER_ADDR = '2a00:1098:82:dd::15:1' SERVER_ADMIN = 'webmaster@societyworks.org' SERVER_NAME = 'labour-mapit.societyworks.org' SERVER_PORT = '80' SERVER_PROTOCOL = 'HTTP/1.1' SERVER_SIGNATURE = '********************' SERVER_SOFTWARE = 'Apache' apache.version = '(2, 4, 46)' mod_wsgi.application_group = 'labour-mapit.societyworks.org|' mod_wsgi.callable_object = 'application' mod_wsgi.daemon_connects = '1' mod_wsgi.daemon_restarts = '0' mod_wsgi.daemon_start = '1657890593149127' mod_wsgi.enable_sendfile = '0' mod_wsgi.handler_script = '' mod_wsgi.ignore_activity = '0' mod_wsgi.listener_host = '' mod_wsgi.listener_port = '8000' mod_wsgi.path_info = '/area/10961.geojson' mod_wsgi.process_group = 'labour-mapit.societyworks.org' mod_wsgi.queue_start = '1657890593147669' mod_wsgi.request_handler = 'wsgi-script' mod_wsgi.request_id = 'UYrhu59OvMM' mod_wsgi.request_start = '1657890593147473' mod_wsgi.script_name = '' mod_wsgi.script_reloading = '1' mod_wsgi.script_start = '1657890593152726' mod_wsgi.thread_id = 4 mod_wsgi.thread_requests = 0 mod_wsgi.total_requests = 43 mod_wsgi.version = '(4, 7, 1)' wsgi.errors = <_io.TextIOWrapper name='' encoding='utf-8'> wsgi.file_wrapper = wsgi.input = wsgi.input_terminated = True wsgi.multiprocess = True wsgi.multithread = True wsgi.run_once = False wsgi.url_scheme = 'http' wsgi.version = '(1, 0)' Settings: Using settings module labour_project.settings [...snipped...] ```
sagepe commented 2 years ago

Noting that we had an object too large for cache error from one of our MapIt instances still using the older MemcachedCache backend when trying to store an object 1.8M in size. Apparently the default object size is 1M for that backend although it can be changed. According to this old Django ticket, this isn't a problem with at least PyLibMCCache.

dracos commented 2 years ago

Is switching to PyLibMCCache just as straightforward or is it more complicated?

davea commented 2 years ago

Probably just as straightforward. pylibmc is written in C whereas pymemcache is pure-Python. Nevertheless the pylibmc wheel installed just fine in a virtualenv on msdev001sova.

sagepe commented 2 years ago

Yes, I think I suggested trying pymemcache first on the basis that it was pure python and the comparison notes on it's PyPI page, but I don't think any of those are critical to us.