Getting/Setting occasionally extremely slow
siovene opened this issue · 5 comments
Hello,
I run a Django website that gets about 3 million requests per day, and I recently started using Sentry to monitor the site's performance and find optimization opportunities.
I noticed that when a certain route was being slow, it was because of a Redis GET or SET taking several hundred milliseconds or even whole seconds, which I found very odd.
My website is hosted on AWS, and the latency between my EC2 instances and my Elasticache cluster is in the order of a few microseconds. I contacted AWS Support to see if they could help, and we examined the Redis logs and saw that there were no slow requests logged. We also conducted a latency test that showed everything was in order.
My first thought was that this could be a serialization/deserialization issue, but this happens also when getting/setting a value with only a few bytes in it, or something atomic like a boolean.
According to Sentry, 50% of my redis requests take less than 0.4 ms (great) but the slowest 1% are slower than 177ms (not great). And I can easily see many that take several seconds.
Now I don't know exactly how Sentry is measuring this, but the same is confirmed by using a second APM tool: Scout APM. This makes me think that something is indeed wrong.
These are the relevant parts of my django-redis configuration:
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': os.environ.get('CACHE_URL', 'redis://redis:6379/1').strip(), # <-- this is my AWS Elastiache Redis endpoint
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PICKLE_VERSION': 2,
'SERIALIZER': 'astrobin.cache.CustomPickleSerializer',
'PARSER_CLASS': 'redis.connection._HiredisParser',
'CONNECTION_POOL_KWARGS': {
'max_connections': 100,
},
},
'KEY_PREFIX': 'astrobin',
'TIMEOUT': 3600,
},
}
import six
from django.utils.encoding import force_bytes
from django_redis.serializers.pickle import PickleSerializer
try:
import cPickle as pickle
except ImportError:
import pickle
class CustomPickleSerializer(PickleSerializer):
def loads(self, value):
if six.PY3:
return self._loads_py3(value)
return super().loads(force_bytes(value))
def _loads_py3(self, value):
return pickle.loads(
force_bytes(value),
fix_imports=True,
encoding='latin1'
)
Am I doing something obviously wrong? Is there something you can suggest I experiment with to try and figure this out?
Here's my versions:
django-redis==5.2.0
hiredis==2.3.2
redis[hiredis]==5.0.1
Thanks!
Salvatore
hello @siovene, if you're looking for performance I would strongly recommend to move away from pickle, maybe plain json would be a good. But if you want to take it further then my suggestion would be to try orjson, not only for redis but also for rendering if you're using django-rest-framework
it would be fairly easy.
In some cases you could also decide to not serialise at all.
I do not know how Scout APM works, I have never used it, but it would be nicer to get more insights into which method is taking long.
Imho sentry does do a good job when it comes to performance measurements, I prefer using new relic for that.
Sentry can be misleading because of the number of traces it analyses and it does not do a good job when trying to understand the big picture especially with multiple services.
Hi @WisdomPill,
I appreciate the tip, I will try JSON or orson. However, do you really think that pickling would be so slow that occasionally it takes 5 seconds to deserialize a 20 byte strings?
it depends on the complexity of the class if you're serialising a class... if you're serialising basic objects (int, string and so on) I would maybe even go so far to not even serialise in order to see if there are any differences.
Thanks @WisdomPill.
Is there a way to specify this on a per-key basis? I just looked around my code base and I cache some simple values, some python dictionaries, but also some entire Django models or even querysets. And I also cache template fragments.
then I would definitely drop pickle and write serializers for your querysets. you could give a try to CacheRouter or use caches directly