Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
Производительность в       Django        Иван Вирабян     i.virabyan@gmail.com                            1
db_index=True не творит             чудесаclass Post(models.Model):       category = models.CharField()       is_editorial...
db_index=True не творит             чудесаclass Post(models.Model):       category = models.CharField(db_index=True)      ...
db_index=True не творит             чудесаclass Post(models.Model):       category = models.CharField(db_index=True)      ...
ЗамерыТаблица содержащая 200 тыс. записей:Три одиночных индекса: 170мсКомбинированный индекс: 1мс                         ...
Пока нет поддержки в              DjangoОчень вероятно, что в Django 1.5 наряду спараметром unique_together появитсяindex_...
db_index=True не творит               чудесаclass Post(models.Model):    category = models.CharField()    is_editorial = m...
Денормализацияclass Post(models.Model):    category = models.CharField(max_length=100)    author = models.ForeignKey(Profi...
mysql> show profile;+----------------------+----------+| Status               | Duration |+----------------------+--------...
Выход есть!class Post(models.Model):    category = models.CharField(max_length=100)    author = models.ForeignKey(Profile,...
Multi-table Inheritance             использовать с осторожностью!class Employee(models.Model):    name = models.CharField(...
Что это запросы?users = User.objects.filter(username__in=[‘vasya’, ‘petya’])Post.objects.filter(author__in=users)Post.obje...
Что это запросы?users = User.objects.filter(username__in=[‘vasya’, ‘petya’])Post.objects.filter(author__in=users)SELECT .....
Django Debug Toolbar                       14
Defer() может быть               медленней!>>> timeit(list(Post.objects.all()[:30]),           from blog.models import Pos...
Причина?В случае с defer() в Model.__init__() значения полей передаются как**kwargsdef __init__(self, *args, **kwargs):   ...
Вывод?.defer() имеет смысл только когда из выборкиисключается большая часть полей. Но тогда прощеиспользовать .only():>>> ...
Запросы в циклеposts = list(Post.objects.all()[:100]){% for post in posts %}     {{ post.author.username }}{% endfor %}В ц...
Запросы в циклеЗамеряем время цикла: ~250мс !?Да-да, мы слышали про select_related(),но неужели БД настолько уныла?Провери...
ncalls tottime cumtime percall    filename:lineno(function)      100    0.002   0.283   0.003   manager.py:131(get)      1...
К чему нам это знать?Вопросы на stackoverflow.com:Say, I have a page with a photo gallery. Each thumbnail hase.g. a photo,...
Шаблонизатор   (безбожно тормозит)                         22
Нужно отрендеритьмного-много всего…На это уходит большая часть времениВремя рендеринга:Django ~1секJinja ~0.3секС Jinja мо...
Рендеринг на клиенте<script type="text/x-jquery-tmpl" id="post-tmpl">    <li>${title} <div>${views}</div></li></script><sc...
Рендеринг на клиенте+ Освобождаются драгоценные ресурсысервера.- Нельзя использовать имеющиеся template-тегии фильтры     ...
Кешированиеval = cache.get("somekey")if val is None:    val = compute_val()    cache.set("somekey", val)Скорость повышаетс...
ИнвалидацияПути решения:• Инвалидация по таймауту    + Легко реализовать    - Не гарантируется актуальность данных•   Инва...
Инвалидация по событию@receiver(post_save, sender=Game)def invalidate_games(**kwargs):    cache.delete(‘game_list’)А что е...
Инвалидация по событиюВариант 1: список всех имеющихся ключейkey = ‘game_list:%s’ % suffixval = cache.get(key)if val is No...
Инвалидация по событиюВариант 2: версионированиеkey = ‘game_list:%s’ % suffixversion = cache.get(‘top_games_version’, 1)va...
Инвалидация по событиюМы используем удобный декоратор:gametags.py:   @register.simple_tag   @cached(vary_on_args=True)   d...
Инвалидация по событиюМы используем удобный декоратор:gametags.py:   @register.simple_tag   @cached(vary_on_args=True, loc...
ОптимизацияИногда есть смысл оптимизировать код,работающий лишь несколько миллисекунд:•   Middleware•   Context processors...
Делайте их ленивымиВы не знаете наверняка, пригодится ли где-нибудь то, чтовы насчитали в своем context processor’е. Поэто...
Nächste SlideShare
Wird geladen in …5
×

Производительность в Django

4.277 Aufrufe

Veröffentlicht am

Заметки к презентации: http://dl.dropbox.com/u/23767208/django-perfomance-notes.pdf

Veröffentlicht in: Technologie

Производительность в Django

  1. 1. Производительность в Django Иван Вирабян i.virabyan@gmail.com 1
  2. 2. db_index=True не творит чудесаclass Post(models.Model): category = models.CharField() is_editorial = models.BooleanField() created = models.DateTimeField()Post.objects.filter(category=‘news’, is_editorial=True) .order_by(‘-created’) 2
  3. 3. db_index=True не творит чудесаclass Post(models.Model): category = models.CharField(db_index=True) is_editorial = models.BooleanField(db_index=True) created = models.DateTimeField(db_index=True)Post.objects.filter(category=‘news’, is_editorial=True) .order_by(‘-created’)[:30] 3
  4. 4. db_index=True не творит чудесаclass Post(models.Model): category = models.CharField(db_index=True) is_editorial = models.BooleanField(db_index=True) created = models.DateTimeField(db_index=True)Post.objects.filter(category=‘news’, is_editorial=True) .order_by(‘-created’)[:30]blog/sql/post.sql (либо в миграции south): CREATE INDEX idx_category_editorial_created ON blog_post(category, is_editorial, created); 4
  5. 5. ЗамерыТаблица содержащая 200 тыс. записей:Три одиночных индекса: 170мсКомбинированный индекс: 1мс 5
  6. 6. Пока нет поддержки в DjangoОчень вероятно, что в Django 1.5 наряду спараметром unique_together появитсяindex_together.https://code.djangoproject.com/ticket/5805 6
  7. 7. 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
  8. 8. Денормализацияclass Post(models.Model): category = models.CharField(max_length=100) author = models.ForeignKey(Profile, related_name=‘posts’)Post.objects.filter(category=‘news’).order_by(‘author__name’)[:30] 8
  9. 9. mysql> show profile;+----------------------+----------+| Status | Duration |+----------------------+----------+| init | 0.000039 || optimizing | 0.000029 || statistics | 0.000148 || preparing | 0.000028 || Creating tmp table | 0.000073 || executing | 0.000010 || Copying to tmp table | 4.347774 || Sorting result | 0.090562 || Sending data | 0.000132 || removing tmp table | 0.000024 || query end | 0.000012 || freeing items | 0.000060 || cleaning up | 0.000014 | 9+----------------------+----------+
  10. 10. Выход есть!class Post(models.Model): category = models.CharField(max_length=100) author = models.ForeignKey(Profile, related_name=‘posts’) author_name = models.CharField(max_length=100)Post.objects.filter(category=‘news’).order_by(‘author_name’)@receiver(signals.post_save, sender=User)def update_author_name(instance, **kwargs): instance.posts.update(author_name=instance.name) 10
  11. 11. 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
  12. 12. Что это запросы?users = User.objects.filter(username__in=[‘vasya’, ‘petya’])Post.objects.filter(author__in=users)Post.objects.filter(author__isnull=True) 12
  13. 13. Что это запросы?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
  14. 14. Django Debug Toolbar 14
  15. 15. Defer() может быть медленней!>>> timeit(list(Post.objects.all()[:30]), from blog.models import Post, number=100)1.3283531665802002>>> timeit(list(Post.objects.defer(“author“, “body”)[:30]), from blog.models import Post, number=100)1.6067261695861816 15
  16. 16. Причина?В случае с defer() в Model.__init__() значения полей передаются как**kwargsdef __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
  17. 17. Вывод?.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
  18. 18. Запросы в циклеposts = list(Post.objects.all()[:100]){% for post in posts %} {{ post.author.username }}{% endfor %}В цикле происходит примерно следующее:User.objects.get(pk=post.author_id) 18
  19. 19. Запросы в циклеЗамеряем время цикла: ~250мс !?Да-да, мы слышали про select_related(),но неужели БД настолько уныла?Проверим! 19
  20. 20. ncalls tottime cumtime percall filename:lineno(function) 100 0.002 0.283 0.003 manager.py:131(get) 100 0.001 0.276 0.003 query.py:337(get)2800/1600 0.001 0.192 0.000 {len} 100 0.001 0.191 0.002 query.py:74(__len__) 200 0.003 0.190 0.001 query.py:214(iterator) 200 0.002 0.172 0.001 compiler.py:673(results_iter) 100 0.001 0.159 0.002 compiler.py:711(execute_sql) 100 0.005 0.086 0.001 util.py:31(execute) 100 0.000 0.075 0.001 base.py:84(execute) 100 0.002 0.075 0.001 cursors.py:139(execute) 100 0.000 0.070 0.001 cursors.py:315(_query) 200 0.002 0.068 0.000 query.py:752(_clone) 200 0.006 0.066 0.000 query.py:223(clone) 100 0.001 0.063 0.001 cursors.py:277(_do_query) 100 0.058 0.058 0.001 {method query of_mysql.connection objects} 4300/800 0.018 0.057 0.000 copy.py:144(deepcopy) 20
  21. 21. К чему нам это знать?Вопросы на stackoverflow.com:Say, I have a page with a photo gallery. Each thumbnail hase.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
  22. 22. Шаблонизатор (безбожно тормозит) 22
  23. 23. Нужно отрендеритьмного-много всего…На это уходит большая часть времениВремя рендеринга:Django ~1секJinja ~0.3секС Jinja можно получитьускорение до 10 раз 23
  24. 24. Рендеринг на клиенте<script type="text/x-jquery-tmpl" id="post-tmpl"> <li>${title} <div>${views}</div></li></script><script type="text/javascript"> $(function() { var template = $(#post-tmpl); $.tmpl(template, {{ posts }}).appendTo(#posts); });</script><ul id=“posts"></ul> 24
  25. 25. Рендеринг на клиенте+ Освобождаются драгоценные ресурсысервера.- Нельзя использовать имеющиеся template-тегии фильтры 25
  26. 26. Кешированиеval = cache.get("somekey")if val is None: val = compute_val() cache.set("somekey", val)Скорость повышается в разы!Но возникает проблема актуальностиданных. 26
  27. 27. ИнвалидацияПути решения:• Инвалидация по таймауту + Легко реализовать - Не гарантируется актуальность данных• Инвалидация по событию + Данные всегда актуальны - Есть некоторые сложности (решаемые) 27
  28. 28. Инвалидация по событию@receiver(post_save, sender=Game)def invalidate_games(**kwargs): cache.delete(‘game_list’)А что если ключ с суффиксом:‘game_list:%s’ % suffixКак инвалидировать все комбинации? 28
  29. 29. Инвалидация по событиюВариант 1: список всех имеющихся ключейkey = ‘game_list:%s’ % suffixval = 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
  30. 30. Инвалидация по событиюВариант 2: версионированиеkey = ‘game_list:%s’ % suffixversion = 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
  31. 31. Инвалидация по событиюМы используем удобный декоратор:gametags.py: @register.simple_tag @cached(vary_on_args=True) def games(platform=None, genre=None): ...signals.py: @receiver(post_save, sender=Game) def inval_games(**kwargs): invalidate(‘games.templatetags.gametags.games’) 31
  32. 32. Инвалидация по событиюМы используем удобный декоратор:gametags.py: @register.simple_tag @cached(vary_on_args=True, locmem=True) def games(platform=None, genre=None): ...signals.py: @receiver(post_save, sender=Game) def inval_games(**kwargs): invalidate(‘games.templatetags.gametags.games’) 32
  33. 33. ОптимизацияИногда есть смысл оптимизировать код,работающий лишь несколько миллисекунд:• Middleware• Context processors• Template tags в базовом шаблонеЕсли среднее время ответа 100мс, а времяработы middleware – 11мс, то снизив его до1мс мы сможем обслуживать на 10%больше запросов. 33
  34. 34. Делайте их ленивымиВы не знаете наверняка, пригодится ли где-нибудь то, чтовы насчитали в своем context processor’е. Поэтомуmiddleware и context processors должны быть ленивыми!from django.utils.functional import lazyclass 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

×