Пока нет поддержки в
Django
Очень вероятно, что в Django 1.5 наряду с
параметром unique_together появится
index_together.
https://code.djangoproject.com/ticket/5805
6
db_index=True не творит
чудеса
class Post(models.Model):
category = models.CharField()
is_editorial = models.BooleanField()
created = models.DateTimeField()
class Meta:
index_together = (‘category’, ‘is_editorial, ‘created’)
Post.objects.filter(category=‘news’, is_editorial=True)
.order_by(‘-created’)[:30]
7
Multi-table Inheritance
использовать с осторожностью!
class Employee(models.Model):
name = models.CharField(max_length=100)
salary = models.PositiveIntegerField()
class Developer(Employee):
lang = models.CharField(max_length=50)
Developer.objects.filter(lang=‘python’).order_by(‘salary’)
медленно при большом объеме данных
11
Что это запросы?
users = User.objects.filter(username__in=[‘vasya’, ‘petya’])
Post.objects.filter(author__in=users)
Post.objects.filter(author__isnull=True)
12
Что это запросы?
users = User.objects.filter(username__in=[‘vasya’, ‘petya’])
Post.objects.filter(author__in=users)
SELECT ... FROM `blog_post`
WHERE `blog_post`.`author_id` IN (
SELECT U0.`id` FROM `auth_user` U0
WHERE U0.`username` IN ('vasya', 'petya'))
Post.objects.filter(author__isnull=True)
SELECT ... FROM `blog_post`
LEFT OUTER JOIN `auth_user`
ON (`blog_post`.`author_id` = `auth_user`.`id`)
WHERE `auth_user`.`id` IS NULL 13
Причина?
В случае с defer() в Model.__init__() значения полей передаются как
**kwargs
def __init__(self, *args, **kwargs):
...
fields_iter = iter(self._meta.fields)
if not kwargs:
for val, field in izip(args, fields_iter):
setattr(self, field.attname, val)
# Далее идет длинный (~70 строк) код для случая с kwargs.
# Этот код работает на 33% медленней.
# Сильно тормозит хак для "умной" обработки ForeignKey полей
16
Вывод?
.defer() имеет смысл только когда из выборки
исключается большая часть полей. Но тогда проще
использовать .only():
>>> timeit('list(Post.objects.only(“title”)[:30])',
'from blog.models import Post', number=100)
0.88186287879943848
Если нужно только данные (без методов) то .values()
будет однозначно быстрее:
>>> timeit('list(Post.objects.values("id", “title")[:30])',
'from blog.models import Post', number=100)
0.25724387168884277
17
Запросы в цикле
posts = list(Post.objects.all()[:100])
{% for post in posts %}
{{ post.author.username }}
{% endfor %}
В цикле происходит примерно следующее:
User.objects.get(pk=post.author_id)
18
Запросы в цикле
Замеряем время цикла: ~250мс !?
Да-да, мы слышали про select_related(),
но неужели БД настолько уныла?
Проверим!
19
К чему нам это знать?
Вопросы на stackoverflow.com:
Say, I have a page with a photo gallery. Each thumbnail has
e.g. a photo, country, author and so on. And it is very slow.
I have performed some profiling using django-debug-toolbar:
SQL Queries: default 84.81 ms (147 queries)
But:
Total CPU time: 5768.360 msec
21
Нужно отрендерить
много-много всего…
На это уходит большая часть времени
Время рендеринга:
Django ~1сек
Jinja ~0.3сек
С Jinja можно получить
ускорение до 10 раз
23
Рендеринг на клиенте
+ Освобождаются драгоценные ресурсы
сервера.
- Нельзя использовать имеющиеся template-теги
и фильтры
25
Кеширование
val = cache.get("somekey")
if val is None:
val = compute_val()
cache.set("somekey", val)
Скорость повышается в разы!
Но возникает проблема актуальности
данных.
26
Инвалидация
Пути решения:
• Инвалидация по таймауту
+ Легко реализовать
- Не гарантируется актуальность данных
• Инвалидация по событию
+ Данные всегда актуальны
- Есть некоторые сложности (решаемые)
27
Инвалидация по событию
@receiver(post_save, sender=Game)
def invalidate_games(**kwargs):
cache.delete(‘game_list’)
А что если ключ с суффиксом:
‘game_list:%s’ % suffix
Как инвалидировать все комбинации?
28
Инвалидация по событию
Вариант 1: список всех имеющихся ключей
key = ‘game_list:%s’ % suffix
val = cache.get(key)
if val is None:
val = ...
cache.set(key, val)
# Запомним этот ключик для последующей инвалидации
keys = cache.get(‘game_list_keys’, [])
cache.set(‘game_list_keys’, set(keys) | set([key]))
# Инвалидация
keys = cache.get(‘game_list_keys’)
cache.delete_many(keys)
cache.delete(‘game_list_keys’) 29
Инвалидация по событию
Вариант 2: версионирование
key = ‘game_list:%s’ % suffix
version = cache.get(‘top_games_version’, 1)
val = cache.get(key, version=version)
if val is None:
val = ...
cache.set(key, val, version=version)
# Инвалидация
try:
cache.incr(‘top_games_version’)
except ValueError:
cache.set(‘top_games_version’, 1)
30
Оптимизация
Иногда есть смысл оптимизировать код,
работающий лишь несколько миллисекунд:
• Middleware
• Context processors
• Template tags в базовом шаблоне
Если среднее время ответа 100мс, а время
работы middleware – 11мс, то снизив его до
1мс мы сможем обслуживать на 10%
больше запросов.
33
Делайте их ленивыми
Вы не знаете наверняка, пригодится ли где-нибудь то, что
вы насчитали в своем context processor’е. Поэтому
middleware и context processors должны быть ленивыми!
from django.utils.functional import lazy
class LocationMiddleware(object):
def process_request(self, request):
request.location = lazy(get_location, dict)(request)
def get_location(request):
g = GeoIP()
remote_ip = request.META.get('REMOTE_ADDR')
return g.city(remote_ip)
34