Retrieve message objects from memcached in a bulk request.

On my laptop, this saves about 80 milliseconds per 1000 messages
requested via get_old_messages queries.  Since we only have one
memcached process and it does not run with special priority, this
might have significant impact on load during server restarts.

(imported from commit 06ad13f32f4a6d87a0664c96297ef9843f410ac5)
This commit is contained in:
Tim Abbott
2013-04-22 10:29:57 -04:00
parent 66b3c1fbff
commit 9b8f0fab0f
3 changed files with 44 additions and 18 deletions

View File

@@ -67,6 +67,13 @@ def cache_with_key(keyfunc, cache_name=None, timeout=None):
return decorator
def cache_get_many(keys, cache_name=None):
if cache_name is None:
cache_backend = djcache
else:
cache_backend = get_cache(cache_name)
return cache_backend.get_many(keys)
def cache(func):
"""Decorator which applies Django caching to a function.

View File

@@ -232,6 +232,9 @@ def get_recipient(type, type_id):
def linebreak(string):
return string.replace('\n\n', '<p/>').replace('\n', '<br/>')
def to_dict_cache_key(message, apply_markdown, rendered_content=None):
return 'message_dict:%d:%d' % (message.id, apply_markdown)
class Message(models.Model):
sender = models.ForeignKey(UserProfile)
recipient = models.ForeignKey(Recipient)
@@ -248,8 +251,7 @@ class Message(models.Model):
def __str__(self):
return self.__repr__()
@cache_with_key(lambda self, apply_markdown, rendered_content=None: 'message_dict:%d:%d' % (self.id, apply_markdown),
timeout=3600*24)
@cache_with_key(to_dict_cache_key, timeout=3600*24)
def to_dict(self, apply_markdown, rendered_content=None):
display_recipient = get_display_recipient(self.recipient)
if self.recipient.type == Recipient.STREAM:

View File

@@ -20,7 +20,7 @@ from zephyr.models import Message, UserProfile, Stream, Subscription, \
Recipient, Realm, UserMessage, \
PreregistrationUser, get_client, MitUser, UserActivity, \
MAX_SUBJECT_LENGTH, get_stream, UserPresence, \
get_recipient, valid_stream_name
get_recipient, valid_stream_name, to_dict_cache_key
from zephyr.lib.actions import do_add_subscription, do_remove_subscription, \
do_change_password, create_mit_user_if_needed, do_change_full_name, \
do_change_enable_desktop_notifications, do_change_enter_sends, \
@@ -44,7 +44,7 @@ from zephyr.lib.query import last_n
from zephyr.lib.avatar import gravatar_hash
from zephyr.lib.response import json_success, json_error, json_response, json_method_not_allowed
from zephyr.lib.timestamp import datetime_to_timestamp
from zephyr.lib.cache import cache_with_key
from zephyr.lib.cache import cache_with_key, cache_get_many
from zephyr.lib.unminify import SourceMap
from zephyr.lib.queue import queue_json_publish
from zephyr.lib.utils import statsd
@@ -707,30 +707,47 @@ def get_old_messages_backend(request, user_profile,
# resulting list always contains the anchor message
if num_before != 0 and num_after == 0:
num_before += 1
messages = last_n(num_before, query.filter(**add_prefix(id__lte=anchor)))
query_result = last_n(num_before, query.filter(**add_prefix(id__lte=anchor)))
elif num_before == 0 and num_after != 0:
num_after += 1
messages = query.filter(**add_prefix(id__gte=anchor))[:num_after]
query_result = query.filter(**add_prefix(id__gte=anchor))[:num_after]
else:
num_after += 1
messages = (last_n(num_before, query.filter(**add_prefix(id__lt=anchor)))
query_result = (last_n(num_before, query.filter(**add_prefix(id__lt=anchor)))
+ list(query.filter(**add_prefix(id__gte=anchor))[:num_after]))
# The following is a little messy, but ensures that the code paths
# are similar regardless of the value of include_history. The
# 'user_messages' dictionary maps each message to the user's
# UserMessage object for that message, which we will attach to the
# rendered message dict before returning it. We attempt to
# bulk-fetch rendered message dicts from memcached using the
# 'messages' list.
if include_history:
user_messages = dict((user_message.message_id, user_message) for user_message in
UserMessage.objects.filter(user_profile=user_profile,
message__in=messages))
message__in=query_result))
messages = query_result
else:
user_messages = dict((user_message.message_id, user_message)
for user_message in query_result)
messages = [user_message.message for user_message in query_result]
bulk_messages = cache_get_many([to_dict_cache_key(message, apply_markdown)
for message in messages])
message_list = []
for message in messages:
if include_history:
flags_dict = {'flags': ["read", "historical"]}
if message.id in user_messages:
flags_dict = user_messages[message.id].flags_dict()
message_list.append(dict(message.to_dict(apply_markdown),
**flags_dict))
data = bulk_messages.get(to_dict_cache_key(message, apply_markdown))
if data is None:
elt = message.to_dict(apply_markdown)
else:
message_list = [dict(umessage.message.to_dict(apply_markdown),
**umessage.flags_dict())
for umessage in messages]
elt = data[0]
message_list.append(dict(elt, **flags_dict))
statsd.incr('loaded_old_messages', len(message_list))
ret = {'messages': message_list,