Your SlideShare is downloading. ×
0
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Производительность в Django
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

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

3,491

Published on

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

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

Published in: Technology
1 Comment
9 Likes
Statistics
Notes
No Downloads
Views
Total Views
3,491
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
33
Comments
1
Likes
9
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Производительность в Django Иван Вирабян i.virabyan@gmail.com 1
  • 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. 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. 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. ЗамерыТаблица содержащая 200 тыс. записей:Три одиночных индекса: 170мсКомбинированный индекс: 1мс 5
  • 6. Пока нет поддержки в DjangoОчень вероятно, что в Django 1.5 наряду спараметром unique_together появитсяindex_together.https://code.djangoproject.com/ticket/5805 6
  • 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. Денормализация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. 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. Выход есть!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. 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. Что это запросы?users = User.objects.filter(username__in=[‘vasya’, ‘petya’])Post.objects.filter(author__in=users)Post.objects.filter(author__isnull=True) 12
  • 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. Django Debug Toolbar 14
  • 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. Причина?В случае с 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. Вывод?.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. Запросы в циклеposts = list(Post.objects.all()[:100]){% for post in posts %} {{ post.author.username }}{% endfor %}В цикле происходит примерно следующее:User.objects.get(pk=post.author_id) 18
  • 19. Запросы в циклеЗамеряем время цикла: ~250мс !?Да-да, мы слышали про select_related(),но неужели БД настолько уныла?Проверим! 19
  • 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. К чему нам это знать?Вопросы на 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
  • 23. Нужно отрендеритьмного-много всего…На это уходит большая часть времениВремя рендеринга:Django ~1секJinja ~0.3секС Jinja можно получитьускорение до 10 раз 23
  • 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. Рендеринг на клиенте+ Освобождаются драгоценные ресурсысервера.- Нельзя использовать имеющиеся template-тегии фильтры 25
  • 26. Кешированиеval = cache.get("somekey")if val is None: val = compute_val() cache.set("somekey", val)Скорость повышается в разы!Но возникает проблема актуальностиданных. 26
  • 27. ИнвалидацияПути решения:• Инвалидация по таймауту + Легко реализовать - Не гарантируется актуальность данных• Инвалидация по событию + Данные всегда актуальны - Есть некоторые сложности (решаемые) 27
  • 28. Инвалидация по событию@receiver(post_save, sender=Game)def invalidate_games(**kwargs): cache.delete(‘game_list’)А что если ключ с суффиксом:‘game_list:%s’ % suffixКак инвалидировать все комбинации? 28
  • 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. Инвалидация по событиюВариант 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. Инвалидация по событиюМы используем удобный декоратор: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. Инвалидация по событиюМы используем удобный декоратор: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. ОптимизацияИногда есть смысл оптимизировать код,работающий лишь несколько миллисекунд:• Middleware• Context processors• Template tags в базовом шаблонеЕсли среднее время ответа 100мс, а времяработы middleware – 11мс, то снизив его до1мс мы сможем обслуживать на 10%больше запросов. 33
  • 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

×