SlideShare a Scribd company logo
Django performance
unchained
Artur Barseghyan
Job Ganzevoort
Goldmund, Wyldebeast & Wunderliebe
http://www.goldmund-wyldebeast-wunderliebe.nl/
Introduction
Restructuring
iSeries
Webserver
Architecture
Bottlenecks
● Server
● Apache
● Database
● Django
● Sessions
● AJAX API
● Search
● iSeries hammering
Server specs
● Virtualized environment
● Single VPS, horizontal scaling possible
● Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz
● 24 cores
● 96GB RAM
● RHEL7
Apache 2.4
● Prefork MPM has a high overhead
● Event MPM scales really well
<IfModule event.c>
StartServers 10
MinSpareThreads 160
MaxSpareThreads 800
ThreadLimit 80
ThreadsPerChild 80
ServerLimit 20
MaxRequestWorkers 1280
MaxRequestsPerChild 0
</IfModule>
Database
● PostgresQL
● Tuning:
max_connections 100 1000
shared_buffers 32MB 8GB
work_mem 1MB
64MB
maintenance_work_mem 16MB 256MB
effective_cache_size 128MB 2GB
log_min_duration_statement -1 500
log_checkpoints off on
log_connections off on
log_disconnections off on
log_line_prefix '' '%d %s
%m: '
log_lock_waits off on
Django
● Running under Gunicorn
● 32 workers
Sessions
● In most cases, we don’t need sessions
● When we do, memcached
AJAX API
● Search
AJAX API
AJAX API
● Campings
● Facet counts
AJAX API
● Rendered results
Search
● Static parameters
● Volatile availability
● Set operations
● Facet counting
● Why not elasticsearch
iSeries API
Varnish
Halfway Summary
● Full-stack approach:
● Tuning of server, database, apache
● Varnish reverse proxy
● API desing
● Smart search filtering/faceting with set operations
● Caching of iSeries calls (Python)
Django optimisations
What costs time (and what can we optimise)?
● ORM
● Database queries
● Abusive cache usage
● Template rendering
But how to measure performance?
django-debug-toolbar
https://pypi.python.org/pypi/django-debug-toolbar
Surprised?
Just joking
django-debug-toolbar-template-profiler
https://pypi.python.org/pypi/django-debug-toolbar-template-profiler
Before we move on
django-debug-toolbar is an excellent tool, but page rendering timings lie.
Let’s start with imaginary app...
Imaginary app (page 1)
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='authors', null=True, blank=True)
Imaginary app (page 2)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('books.Author', related_name='books')
publisher = models.ForeignKey(Publisher, related_name='books')
publication_date = models.DateField()
isbn = models.CharField(max_length=100, unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
pages = models.PositiveIntegerField(default=200)
stock_count = models.PositiveIntegerField(default=30)
Imaginary app (page 3)
class Order(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
lines = models.ManyToManyField("books.OrderLine", blank=True)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
class OrderLine(models.Model):
book = models.ForeignKey('books.Book', related_name='order_lines')
ORM & Database queries
Tips:
● Make use of `select_related` and `prefetch_related` for fetching related
objects at once.
● Make use of `only` and `defer` (typical use case: listing views), but use
them with caution.
● Use `annotate` and `aggregate` for calculations on database level.
● When fetching IDs of related item (if you only need ID), use
{field_name}_id instead of {field_name}.id, since the first one does not
cause table JOIN.
● Consider using `values` or `values_list` for listing views, when possible.
Use case
Listing of books (about 2000 in total) with the following information:
● Book title
● Names of all authors of the book separated by comma
● Publisher name
● Number of pages
● Price
Bad example
books = Book.objects.all()
Results
4023 queries took 730 ms, page rendered in 23932 ms
Let’s improve
Adding:
● select_related
● prefetch_related
● only
Good example
books = Book.objects.all() 
.select_related('publisher') 
.prefetch_related('authors') 
.only('id', 'title', 'pages', 'price',
'publisher__id', 'publisher__name',
'authors__id', 'authors__name')
Results
2 queries took 12 ms, page rendered in 2104 ms
Let’s improve more
Adding:
● values
● annotate
Even better example
books = Book.objects.all() 
.values('id', 'title', 'pages', 'price',
'publisher__id', 'publisher__name') 
.annotate(
authors__name=GroupConcat('authors__name', separator=', ')
) 
.distinct()
Results
1 query took 13 ms, page rendered in 568 ms
But what is GroupConcat?
But what is GroupConcat?
Custom aggregation functions
from django.db.models import Aggregate
class GroupConcat(Aggregate):
function = 'group_concat'
@property
def template(self):
separator = self.extra.get('separator')
if separator:
return '%(function)s(%(field)s, "%(separator)s")'
else:
return '%(function)s(%(field)s)'
Surprised?
Avoid database hits in the loop!
Operate in batch as much as possible!
Use case
Create 50 authors
Bad example
for __i in range(1, 50):
author = Author.objects.create(
salutation='Something %s' % uuid.uuid4(),
name='Some name %s' % uuid.uuid4(),
email='name%s@example.com' % uuid.uuid4()
)
Results
98 queries took 40 ms, page rendered in 652 ms
Let’s improve
Adding:
● bulk_create
Good example
authors_list = []
for __i in range(1, 50):
author = Author(
salutation='Something %s' % uuid.uuid4(),
name='Some name %s' % uuid.uuid4(),
email='name%s@example.com' % uuid.uuid4()
)
authors_list.append(author)
Author.objects.bulk_create(authors_list)
Results
2 queries took 1.73 ms, page rendered in 60 ms
If you use django-debug-toolbar already...
...you may say
I know it, django-debug-toolbar is great...
...but what about non-HTML (JSON, partial HTML) views?
django-debug-toolbar-force
https://pypi.python.org/pypi/django-debug-toolbar-force
MIDDLEWARE_CLASSES += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
'debug_toolbar_force.middleware.ForceDebugToolbarMiddleware',
)
And add ?debug-toolbar to any non-HTML (JSON, AJAX) view URL
Surprised?
Hey, enough with these faces!
Fine. Let’s move on...
One important thing!
And you’ll hear it many times
in this presentation...
Avoid querying in the loop!
Given the following model...
class Item(models.Model):
text = models.TextField()
page_id = models.IntegerField()
...where page_id is the correspondent ID
of the CMS page
But WHY???
Because it happens and you have to deal with it!
Surprised?
Bad example
items = Item.objects.all()
for item in items:
page = Page.objects.get(id=item.page_id)
# ... do something else with item
Results
101 database queries
Improved example
# Freeze the queryset
items = Item.objects.all()[:]
# Collect all page ids
page_ids = [item.page_id for item in items]
pages = Page.objects.filter(id__in=page_ids)
pages_dict = {page.id: page for page in pages}
for item in items:
page = pages_dict.get(item.page_id)
# ... do something else with item
Results
No additional database queries and no missed cache hits.
Unnecessary cache hits
Tips:
● Analyze cache usage with django-debug-toolbar.
● Use template fragment caching to minimize the number of cache queries.
● Identify heavy parts of your templates with TemplateProfilerPanel.
Optimise them first and after that, if they are still heavy, cache them.
● Try to avoid repetitive missed cache queries.
● The well known {% page_url %} and {% page_attribute %} tags of
DjangoCMS may produce a lot of missed cache queries.
● Document cache usages. Explain in the code why do you cache that
certain part. Keep track of all cache usages in a separate section of your
developer documentation.
Optimise templates
Tips:
● Clean up your templates. Remove all unused import statements.
● Use cached template loading on production.
● Avoid database queries done in the loop. Especially {% page_url %} and
{% page_attribute %} template tags of DjangoCMS.
A couple of things
● Try to reduce the number of database queries
● Use EXPLAIN when things are still slow.
● Add indexes when necessary.
● Compare the "before" and "after". Do it often.
● Test page load speed with caching on and off.
● Test the first page load and the second page load. Analyze the difference.
● Try to reduce the number of missed cache queries.
Tips
● Try to get rid of context processors and middleware that hit the database.
● Be careful with templatetags that query the database/cache. Especially,
when they are done in a loop.
DjangoCMS specific tricks
DjangoCMS is fine...
...but all your non-CMS views would be affected with
additional checks and queries, even if they are totally
irrelevant.
Scary?
Hold on, there’s a way to fix it!
# Some sort of a fake page container, to trick django-cms.
PageData = namedtuple('PageData', ['pk'])
class MyView(View):
def get(self, request):
# This is to trick the django-cms middleware, so that we don't
# fetch page from the request (not needed here).
request._current_page_cache = PageData("_")
context = self.get_context(request)
return render_to_response(
self.template_name),
context,
request
)
Now you non-CMS views are clean
Surprised?
Dealing with {% page_url %}
Avoid:
● Use of {% page_url %} or {% page_attribute %} tags on IDs. Pass
complete objects only.
● Apply tricks to the querysets to fetch the page objects efficiently.
● If you list a lot of DjangoCMS Page objects (with URLs) on a page, don’t
use {% page_url %} at all. Instead, use customized template tag which fits
your needs better.
Use case
Sitemap page with about 200 CMS pages.
Bad example
pages = Page.objects.all()
{% for page in pages %}
{% page_url page.id %}
{% endfor %}
Results
2000 database queries and 3000 missed cached hits
Improved example (page 1)
pages = Page.objects.all()[]
page_ids = [page.id for page in pages]
titles = Title.objects 
.filter(language=get_language()) 
.filter(page__id__in=page_ids) 
.select_related('page', 'page__publisher_public', 'page__site') 
.only('id', 'title', 'slug', 'path', 'page__id', 'page__reverse_id',
'page__publisher_is_draft',
'page__publisher_public__publisher_is_draft',
'page__publication_date', 'page__publication_end_date',
'page__site', 'page__is_home', 'page__revision_id')[:]
Improved example (page 2)
pages_dict = {}
for _title in titles:
if not hasattr(_title.page, 'title_cache'):
_title.page.title_cache = {}
_title.page.title_cache[get_language()] = _title
pages_dict[_title.page.id] = _title.page
_pages = []
for page in pages:
_pages.append(page_dict.get(page.id, page))
pages = _pages
Improved example (page 3)
class AddonsPageUrlNoCache(PageUrl):
"""PageUrl made that doesn't hit the cache.
You should not be using this everywhere, however if you know what you're
doing, it can save you a lot of page rendering time.
"""
name = 'addons_page_url_no_cache'
Improved example (page 4)
def get_value(self, context, page_lookup, lang, site):
site_id = get_site_id(site)
request = context.get('request', False)
if not request:
return ''
if lang is None:
lang = get_language_from_request(request)
page = _get_page_by_untyped_arg(page_lookup, request, site_id)
if page:
url = page.get_absolute_url(language=lang)
if url:
return url
return ''
Improved example (page 5)
register.tag(AddonsPageUrlNoCache)
{% for page in pages %}
{% addons_page_url_no_cache page %}
{% endfor %}
Results
No additional database queries and no missed cache hits.
Surprised?
Use get_render_queryset on CMS plugins
Use get_render_queryset class method to fetch objects efficiently.
Use case
Write a CMS plugin which has 2 foreign key relations.
Bad example
class TextPictureLink(AbstractText):
title = models.TextField(_("Title"), null=True, blank=True)
image = FilerImageField(null=True, blank=True)
page_link = PageField(null=True, blank=True)
class GenericContentPlugin(TextPlugin):
module = _('Blocks')
render_template = 'path/to/generic_text_plugin.html'
name = _('Generic Plugin')
model = models.TextPictureLink
Results
3 database hits for a single plugin. How many plugins do you have on a page?
Good example
class GenericContentPlugin(TextPlugin):
# ... original code
@classmethod
def get_render_queryset(cls):
"""Tweak the queryset."""
return cls.model._default_manager 
.all() 
.select_related('image', 'page_link')
Results
1 database hit for a single plugin.
Load testing
Apache JMeter
Highlights:
● Load/stress testing tool for analyzing/measuring the performance.
● Focused on web applications.
● Plugin based architecture.
● Supports variable parametrisation, assertions (response validation), per-
thread cookies, configuration variables.
● Comes with a large variety of reports (aggregations, graphs).
● Can replay access logs, including Apache2, Nginx, Tomcat and more.
● You can save your tests and repeat them later.
We measured “before”...
We measured “after”...
And “after” was a way better!
We’re happy
Linkodrome
1 / n
This presentation on slideshare
https://www.slideshare.net/barseghyanartur/pygrunn-2017-django-performance-unchained-slides
django-debug-toolbar-template-profiler https://pypi.python.org/pypi/django-debug-toolbar-template-
profiler
django-debug-toolbar-force
https://pypi.python.org/pypi/django-debug-toolbar-force
Apache JMeter
http://jmeter.apache.org/
Custom aggregation functions
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#creating-your-own-aggregate-functions
Linkodrome (page 2)
1 / n
My GitHub account
https://github.com/barseghyanartur
django-debug-toolbar
https://pypi.python.org/pypi/django-debug-toolbar
Questions?
Thank you!
Artur Barseghyan
Job Ganzevoort
Goldmund, Wyldebeast & Wunderliebe
http://www.goldmund-wyldebeast-wunderliebe.nl/
PyGrunn 2017 - Django Performance Unchained - slides

More Related Content

What's hot

Introduction to node.js
Introduction to node.jsIntroduction to node.js
Introduction to node.jsAdrien Guéret
 
Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Adam Tomat
 
Django Framework and Application Structure
Django Framework and Application StructureDjango Framework and Application Structure
Django Framework and Application StructureSEONGTAEK OH
 
Rails vs Web2py
Rails vs Web2pyRails vs Web2py
Rails vs Web2pyjonromero
 
Let's write secure drupal code! - Drupal Camp Pannonia 2019
Let's write secure drupal code! - Drupal Camp Pannonia 2019Let's write secure drupal code! - Drupal Camp Pannonia 2019
Let's write secure drupal code! - Drupal Camp Pannonia 2019Balázs Tatár
 
High Performance Django
High Performance DjangoHigh Performance Django
High Performance DjangoDjangoCon2008
 
Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!Jeff Jones
 
When To Use Ruby On Rails
When To Use Ruby On RailsWhen To Use Ruby On Rails
When To Use Ruby On Railsdosire
 
Django for mobile applications
Django for mobile applicationsDjango for mobile applications
Django for mobile applicationsHassan Abid
 
memories of tumblr gear & Tumblrowl
memories of tumblr gear & Tumblrowlmemories of tumblr gear & Tumblrowl
memories of tumblr gear & Tumblrowlhonishi
 
Migrating PriceChirp to Rails 3.0: The Pain Points
Migrating PriceChirp to Rails 3.0: The Pain PointsMigrating PriceChirp to Rails 3.0: The Pain Points
Migrating PriceChirp to Rails 3.0: The Pain PointsSteven Evatt
 
Multi Tenancy With Python and Django
Multi Tenancy With Python and DjangoMulti Tenancy With Python and Django
Multi Tenancy With Python and Djangoscottcrespo
 
Beyond JVM - YOW! Sydney 2013
Beyond JVM - YOW! Sydney 2013Beyond JVM - YOW! Sydney 2013
Beyond JVM - YOW! Sydney 2013Charles Nutter
 
Ruby on Rails Security
Ruby on Rails SecurityRuby on Rails Security
Ruby on Rails Securityamiable_indian
 
Building web framework with Rack
Building web framework with RackBuilding web framework with Rack
Building web framework with Racksickill
 

What's hot (20)

Introduction to node.js
Introduction to node.jsIntroduction to node.js
Introduction to node.js
 
Php on Windows
Php on WindowsPhp on Windows
Php on Windows
 
Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018
 
Django Framework and Application Structure
Django Framework and Application StructureDjango Framework and Application Structure
Django Framework and Application Structure
 
Rails vs Web2py
Rails vs Web2pyRails vs Web2py
Rails vs Web2py
 
WordPress and Ajax
WordPress and AjaxWordPress and Ajax
WordPress and Ajax
 
Let's write secure drupal code! - Drupal Camp Pannonia 2019
Let's write secure drupal code! - Drupal Camp Pannonia 2019Let's write secure drupal code! - Drupal Camp Pannonia 2019
Let's write secure drupal code! - Drupal Camp Pannonia 2019
 
High Performance Django
High Performance DjangoHigh Performance Django
High Performance Django
 
Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!
 
A look at Drupal 7 Theming
A look at Drupal 7 ThemingA look at Drupal 7 Theming
A look at Drupal 7 Theming
 
When To Use Ruby On Rails
When To Use Ruby On RailsWhen To Use Ruby On Rails
When To Use Ruby On Rails
 
Django for mobile applications
Django for mobile applicationsDjango for mobile applications
Django for mobile applications
 
memories of tumblr gear & Tumblrowl
memories of tumblr gear & Tumblrowlmemories of tumblr gear & Tumblrowl
memories of tumblr gear & Tumblrowl
 
Migrating PriceChirp to Rails 3.0: The Pain Points
Migrating PriceChirp to Rails 3.0: The Pain PointsMigrating PriceChirp to Rails 3.0: The Pain Points
Migrating PriceChirp to Rails 3.0: The Pain Points
 
Multi Tenancy With Python and Django
Multi Tenancy With Python and DjangoMulti Tenancy With Python and Django
Multi Tenancy With Python and Django
 
Django at Scale
Django at ScaleDjango at Scale
Django at Scale
 
Hppg
HppgHppg
Hppg
 
Beyond JVM - YOW! Sydney 2013
Beyond JVM - YOW! Sydney 2013Beyond JVM - YOW! Sydney 2013
Beyond JVM - YOW! Sydney 2013
 
Ruby on Rails Security
Ruby on Rails SecurityRuby on Rails Security
Ruby on Rails Security
 
Building web framework with Rack
Building web framework with RackBuilding web framework with Rack
Building web framework with Rack
 

Similar to PyGrunn 2017 - Django Performance Unchained - slides

Journey through high performance django application
Journey through high performance django applicationJourney through high performance django application
Journey through high performance django applicationbangaloredjangousergroup
 
High Performance Django 1
High Performance Django 1High Performance Django 1
High Performance Django 1DjangoCon2008
 
Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...
Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...
Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...Holden Karau
 
Automate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scala
Automate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scalaAutomate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scala
Automate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scalaChetan Khatri
 
Performance Optimization of Rails Applications
Performance Optimization of Rails ApplicationsPerformance Optimization of Rails Applications
Performance Optimization of Rails ApplicationsSerge Smetana
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to DjangoJames Casey
 
Nancy CLI. Automated Database Experiments
Nancy CLI. Automated Database ExperimentsNancy CLI. Automated Database Experiments
Nancy CLI. Automated Database ExperimentsNikolay Samokhvalov
 
Practical catalyst
Practical catalystPractical catalyst
Practical catalystdwm042
 
TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...
TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...
TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...Chetan Khatri
 
Magento Performance Optimization 101
Magento Performance Optimization 101Magento Performance Optimization 101
Magento Performance Optimization 101Angus Li
 
Killing the Angle Bracket
Killing the Angle BracketKilling the Angle Bracket
Killing the Angle Bracketjnewmanux
 
Dart the Better JavaScript
Dart the Better JavaScriptDart the Better JavaScript
Dart the Better JavaScriptJorg Janke
 
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and MingRapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and MingRick Copeland
 
MongoDB hearts Django? (Django NYC)
MongoDB hearts Django? (Django NYC)MongoDB hearts Django? (Django NYC)
MongoDB hearts Django? (Django NYC)Mike Dirolf
 
Web performance essentials - Goodies
Web performance essentials - GoodiesWeb performance essentials - Goodies
Web performance essentials - GoodiesJerry Emmanuel
 
Good practices for PrestaShop code security and optimization
Good practices for PrestaShop code security and optimizationGood practices for PrestaShop code security and optimization
Good practices for PrestaShop code security and optimizationPrestaShop
 
Intro to mobile web application development
Intro to mobile web application developmentIntro to mobile web application development
Intro to mobile web application developmentzonathen
 
Sinatra and JSONQuery Web Service
Sinatra and JSONQuery Web ServiceSinatra and JSONQuery Web Service
Sinatra and JSONQuery Web Servicevvatikiotis
 

Similar to PyGrunn 2017 - Django Performance Unchained - slides (20)

Journey through high performance django application
Journey through high performance django applicationJourney through high performance django application
Journey through high performance django application
 
High Performance Django 1
High Performance Django 1High Performance Django 1
High Performance Django 1
 
Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...
Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...
Ml pipelines with Apache spark and Apache beam - Ottawa Reactive meetup Augus...
 
Automate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scala
Automate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scalaAutomate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scala
Automate ml workflow_transmogrif_ai-_chetan_khatri_berlin-scala
 
Performance Optimization of Rails Applications
Performance Optimization of Rails ApplicationsPerformance Optimization of Rails Applications
Performance Optimization of Rails Applications
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
 
Nancy CLI. Automated Database Experiments
Nancy CLI. Automated Database ExperimentsNancy CLI. Automated Database Experiments
Nancy CLI. Automated Database Experiments
 
Introduce Django
Introduce DjangoIntroduce Django
Introduce Django
 
Practical catalyst
Practical catalystPractical catalyst
Practical catalyst
 
TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...
TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...
TransmogrifAI - Automate Machine Learning Workflow with the power of Scala an...
 
Magento Performance Optimization 101
Magento Performance Optimization 101Magento Performance Optimization 101
Magento Performance Optimization 101
 
Killing the Angle Bracket
Killing the Angle BracketKilling the Angle Bracket
Killing the Angle Bracket
 
Dart the Better JavaScript
Dart the Better JavaScriptDart the Better JavaScript
Dart the Better JavaScript
 
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and MingRapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
 
MongoDB hearts Django? (Django NYC)
MongoDB hearts Django? (Django NYC)MongoDB hearts Django? (Django NYC)
MongoDB hearts Django? (Django NYC)
 
Web performance essentials - Goodies
Web performance essentials - GoodiesWeb performance essentials - Goodies
Web performance essentials - Goodies
 
Ot performance webinar
Ot performance webinarOt performance webinar
Ot performance webinar
 
Good practices for PrestaShop code security and optimization
Good practices for PrestaShop code security and optimizationGood practices for PrestaShop code security and optimization
Good practices for PrestaShop code security and optimization
 
Intro to mobile web application development
Intro to mobile web application developmentIntro to mobile web application development
Intro to mobile web application development
 
Sinatra and JSONQuery Web Service
Sinatra and JSONQuery Web ServiceSinatra and JSONQuery Web Service
Sinatra and JSONQuery Web Service
 

Recently uploaded

FIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdfFIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdfFIDO Alliance
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf91mobiles
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backElena Simperl
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...Sri Ambati
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)Ralf Eggert
 
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsPaul Groth
 
UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2DianaGray10
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform EngineeringJemma Hussein Allen
 
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptxUnpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptxDavid Michel
 
In-Depth Performance Testing Guide for IT Professionals
In-Depth Performance Testing Guide for IT ProfessionalsIn-Depth Performance Testing Guide for IT Professionals
In-Depth Performance Testing Guide for IT ProfessionalsExpeed Software
 
Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Frank van Harmelen
 
UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1DianaGray10
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesThousandEyes
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Product School
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...Elena Simperl
 
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀DianaGray10
 
Speed Wins: From Kafka to APIs in Minutes
Speed Wins: From Kafka to APIs in MinutesSpeed Wins: From Kafka to APIs in Minutes
Speed Wins: From Kafka to APIs in Minutesconfluent
 
"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor TurskyiFwdays
 
Search and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesSearch and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesBhaskar Mitra
 
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlPeter Udo Diehl
 

Recently uploaded (20)

FIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdfFIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdf
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
 
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
 
UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2UiPath Test Automation using UiPath Test Suite series, part 2
UiPath Test Automation using UiPath Test Suite series, part 2
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
 
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptxUnpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
 
In-Depth Performance Testing Guide for IT Professionals
In-Depth Performance Testing Guide for IT ProfessionalsIn-Depth Performance Testing Guide for IT Professionals
In-Depth Performance Testing Guide for IT Professionals
 
Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*
 
UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...
 
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
 
Speed Wins: From Kafka to APIs in Minutes
Speed Wins: From Kafka to APIs in MinutesSpeed Wins: From Kafka to APIs in Minutes
Speed Wins: From Kafka to APIs in Minutes
 
"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi
 
Search and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesSearch and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical Futures
 
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
 

PyGrunn 2017 - Django Performance Unchained - slides

  • 1. Django performance unchained Artur Barseghyan Job Ganzevoort Goldmund, Wyldebeast & Wunderliebe http://www.goldmund-wyldebeast-wunderliebe.nl/
  • 3.
  • 6. Bottlenecks ● Server ● Apache ● Database ● Django ● Sessions ● AJAX API ● Search ● iSeries hammering
  • 7. Server specs ● Virtualized environment ● Single VPS, horizontal scaling possible ● Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz ● 24 cores ● 96GB RAM ● RHEL7
  • 8. Apache 2.4 ● Prefork MPM has a high overhead ● Event MPM scales really well <IfModule event.c> StartServers 10 MinSpareThreads 160 MaxSpareThreads 800 ThreadLimit 80 ThreadsPerChild 80 ServerLimit 20 MaxRequestWorkers 1280 MaxRequestsPerChild 0 </IfModule>
  • 9. Database ● PostgresQL ● Tuning: max_connections 100 1000 shared_buffers 32MB 8GB work_mem 1MB 64MB maintenance_work_mem 16MB 256MB effective_cache_size 128MB 2GB log_min_duration_statement -1 500 log_checkpoints off on log_connections off on log_disconnections off on log_line_prefix '' '%d %s %m: ' log_lock_waits off on
  • 10. Django ● Running under Gunicorn ● 32 workers
  • 11. Sessions ● In most cases, we don’t need sessions ● When we do, memcached
  • 16. Search ● Static parameters ● Volatile availability ● Set operations ● Facet counting ● Why not elasticsearch
  • 19. Halfway Summary ● Full-stack approach: ● Tuning of server, database, apache ● Varnish reverse proxy ● API desing ● Smart search filtering/faceting with set operations ● Caching of iSeries calls (Python)
  • 20. Django optimisations What costs time (and what can we optimise)? ● ORM ● Database queries ● Abusive cache usage ● Template rendering
  • 21. But how to measure performance?
  • 26. Before we move on django-debug-toolbar is an excellent tool, but page rendering timings lie.
  • 27. Let’s start with imaginary app...
  • 28. Imaginary app (page 1) class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() class Author(models.Model): salutation = models.CharField(max_length=10) name = models.CharField(max_length=200) email = models.EmailField() headshot = models.ImageField(upload_to='authors', null=True, blank=True)
  • 29. Imaginary app (page 2) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField('books.Author', related_name='books') publisher = models.ForeignKey(Publisher, related_name='books') publication_date = models.DateField() isbn = models.CharField(max_length=100, unique=True) price = models.DecimalField(max_digits=10, decimal_places=2) pages = models.PositiveIntegerField(default=200) stock_count = models.PositiveIntegerField(default=30)
  • 30. Imaginary app (page 3) class Order(models.Model): owner = models.ForeignKey(settings.AUTH_USER_MODEL) lines = models.ManyToManyField("books.OrderLine", blank=True) created = models.DateField(auto_now_add=True) updated = models.DateField(auto_now=True) class OrderLine(models.Model): book = models.ForeignKey('books.Book', related_name='order_lines')
  • 31. ORM & Database queries Tips: ● Make use of `select_related` and `prefetch_related` for fetching related objects at once. ● Make use of `only` and `defer` (typical use case: listing views), but use them with caution. ● Use `annotate` and `aggregate` for calculations on database level. ● When fetching IDs of related item (if you only need ID), use {field_name}_id instead of {field_name}.id, since the first one does not cause table JOIN. ● Consider using `values` or `values_list` for listing views, when possible.
  • 32. Use case Listing of books (about 2000 in total) with the following information: ● Book title ● Names of all authors of the book separated by comma ● Publisher name ● Number of pages ● Price
  • 33. Bad example books = Book.objects.all()
  • 34. Results 4023 queries took 730 ms, page rendered in 23932 ms
  • 36. Good example books = Book.objects.all() .select_related('publisher') .prefetch_related('authors') .only('id', 'title', 'pages', 'price', 'publisher__id', 'publisher__name', 'authors__id', 'authors__name')
  • 37. Results 2 queries took 12 ms, page rendered in 2104 ms
  • 38. Let’s improve more Adding: ● values ● annotate
  • 39. Even better example books = Book.objects.all() .values('id', 'title', 'pages', 'price', 'publisher__id', 'publisher__name') .annotate( authors__name=GroupConcat('authors__name', separator=', ') ) .distinct()
  • 40. Results 1 query took 13 ms, page rendered in 568 ms
  • 41. But what is GroupConcat? But what is GroupConcat?
  • 42. Custom aggregation functions from django.db.models import Aggregate class GroupConcat(Aggregate): function = 'group_concat' @property def template(self): separator = self.extra.get('separator') if separator: return '%(function)s(%(field)s, "%(separator)s")' else: return '%(function)s(%(field)s)'
  • 44. Avoid database hits in the loop! Operate in batch as much as possible!
  • 46. Bad example for __i in range(1, 50): author = Author.objects.create( salutation='Something %s' % uuid.uuid4(), name='Some name %s' % uuid.uuid4(), email='name%s@example.com' % uuid.uuid4() )
  • 47. Results 98 queries took 40 ms, page rendered in 652 ms
  • 49. Good example authors_list = [] for __i in range(1, 50): author = Author( salutation='Something %s' % uuid.uuid4(), name='Some name %s' % uuid.uuid4(), email='name%s@example.com' % uuid.uuid4() ) authors_list.append(author) Author.objects.bulk_create(authors_list)
  • 50. Results 2 queries took 1.73 ms, page rendered in 60 ms
  • 51. If you use django-debug-toolbar already... ...you may say
  • 52. I know it, django-debug-toolbar is great... ...but what about non-HTML (JSON, partial HTML) views?
  • 55. Hey, enough with these faces!
  • 57. One important thing! And you’ll hear it many times in this presentation...
  • 58. Avoid querying in the loop!
  • 59. Given the following model... class Item(models.Model): text = models.TextField() page_id = models.IntegerField() ...where page_id is the correspondent ID of the CMS page
  • 61. Because it happens and you have to deal with it!
  • 63. Bad example items = Item.objects.all() for item in items: page = Page.objects.get(id=item.page_id) # ... do something else with item
  • 65. Improved example # Freeze the queryset items = Item.objects.all()[:] # Collect all page ids page_ids = [item.page_id for item in items] pages = Page.objects.filter(id__in=page_ids) pages_dict = {page.id: page for page in pages} for item in items: page = pages_dict.get(item.page_id) # ... do something else with item
  • 66. Results No additional database queries and no missed cache hits.
  • 67. Unnecessary cache hits Tips: ● Analyze cache usage with django-debug-toolbar. ● Use template fragment caching to minimize the number of cache queries. ● Identify heavy parts of your templates with TemplateProfilerPanel. Optimise them first and after that, if they are still heavy, cache them. ● Try to avoid repetitive missed cache queries. ● The well known {% page_url %} and {% page_attribute %} tags of DjangoCMS may produce a lot of missed cache queries. ● Document cache usages. Explain in the code why do you cache that certain part. Keep track of all cache usages in a separate section of your developer documentation.
  • 68. Optimise templates Tips: ● Clean up your templates. Remove all unused import statements. ● Use cached template loading on production. ● Avoid database queries done in the loop. Especially {% page_url %} and {% page_attribute %} template tags of DjangoCMS.
  • 69. A couple of things ● Try to reduce the number of database queries ● Use EXPLAIN when things are still slow. ● Add indexes when necessary. ● Compare the "before" and "after". Do it often. ● Test page load speed with caching on and off. ● Test the first page load and the second page load. Analyze the difference. ● Try to reduce the number of missed cache queries.
  • 70. Tips ● Try to get rid of context processors and middleware that hit the database. ● Be careful with templatetags that query the database/cache. Especially, when they are done in a loop.
  • 72. DjangoCMS is fine... ...but all your non-CMS views would be affected with additional checks and queries, even if they are totally irrelevant.
  • 74. Hold on, there’s a way to fix it! # Some sort of a fake page container, to trick django-cms. PageData = namedtuple('PageData', ['pk']) class MyView(View): def get(self, request): # This is to trick the django-cms middleware, so that we don't # fetch page from the request (not needed here). request._current_page_cache = PageData("_") context = self.get_context(request) return render_to_response( self.template_name), context, request )
  • 75. Now you non-CMS views are clean
  • 77. Dealing with {% page_url %} Avoid: ● Use of {% page_url %} or {% page_attribute %} tags on IDs. Pass complete objects only. ● Apply tricks to the querysets to fetch the page objects efficiently. ● If you list a lot of DjangoCMS Page objects (with URLs) on a page, don’t use {% page_url %} at all. Instead, use customized template tag which fits your needs better.
  • 78. Use case Sitemap page with about 200 CMS pages.
  • 79. Bad example pages = Page.objects.all() {% for page in pages %} {% page_url page.id %} {% endfor %}
  • 80. Results 2000 database queries and 3000 missed cached hits
  • 81. Improved example (page 1) pages = Page.objects.all()[] page_ids = [page.id for page in pages] titles = Title.objects .filter(language=get_language()) .filter(page__id__in=page_ids) .select_related('page', 'page__publisher_public', 'page__site') .only('id', 'title', 'slug', 'path', 'page__id', 'page__reverse_id', 'page__publisher_is_draft', 'page__publisher_public__publisher_is_draft', 'page__publication_date', 'page__publication_end_date', 'page__site', 'page__is_home', 'page__revision_id')[:]
  • 82. Improved example (page 2) pages_dict = {} for _title in titles: if not hasattr(_title.page, 'title_cache'): _title.page.title_cache = {} _title.page.title_cache[get_language()] = _title pages_dict[_title.page.id] = _title.page _pages = [] for page in pages: _pages.append(page_dict.get(page.id, page)) pages = _pages
  • 83. Improved example (page 3) class AddonsPageUrlNoCache(PageUrl): """PageUrl made that doesn't hit the cache. You should not be using this everywhere, however if you know what you're doing, it can save you a lot of page rendering time. """ name = 'addons_page_url_no_cache'
  • 84. Improved example (page 4) def get_value(self, context, page_lookup, lang, site): site_id = get_site_id(site) request = context.get('request', False) if not request: return '' if lang is None: lang = get_language_from_request(request) page = _get_page_by_untyped_arg(page_lookup, request, site_id) if page: url = page.get_absolute_url(language=lang) if url: return url return ''
  • 85. Improved example (page 5) register.tag(AddonsPageUrlNoCache) {% for page in pages %} {% addons_page_url_no_cache page %} {% endfor %}
  • 86. Results No additional database queries and no missed cache hits.
  • 88. Use get_render_queryset on CMS plugins Use get_render_queryset class method to fetch objects efficiently.
  • 89. Use case Write a CMS plugin which has 2 foreign key relations.
  • 90. Bad example class TextPictureLink(AbstractText): title = models.TextField(_("Title"), null=True, blank=True) image = FilerImageField(null=True, blank=True) page_link = PageField(null=True, blank=True) class GenericContentPlugin(TextPlugin): module = _('Blocks') render_template = 'path/to/generic_text_plugin.html' name = _('Generic Plugin') model = models.TextPictureLink
  • 91. Results 3 database hits for a single plugin. How many plugins do you have on a page?
  • 92. Good example class GenericContentPlugin(TextPlugin): # ... original code @classmethod def get_render_queryset(cls): """Tweak the queryset.""" return cls.model._default_manager .all() .select_related('image', 'page_link')
  • 93. Results 1 database hit for a single plugin.
  • 95. Apache JMeter Highlights: ● Load/stress testing tool for analyzing/measuring the performance. ● Focused on web applications. ● Plugin based architecture. ● Supports variable parametrisation, assertions (response validation), per- thread cookies, configuration variables. ● Comes with a large variety of reports (aggregations, graphs). ● Can replay access logs, including Apache2, Nginx, Tomcat and more. ● You can save your tests and repeat them later.
  • 98. And “after” was a way better!
  • 100. Linkodrome 1 / n This presentation on slideshare https://www.slideshare.net/barseghyanartur/pygrunn-2017-django-performance-unchained-slides django-debug-toolbar-template-profiler https://pypi.python.org/pypi/django-debug-toolbar-template- profiler django-debug-toolbar-force https://pypi.python.org/pypi/django-debug-toolbar-force Apache JMeter http://jmeter.apache.org/ Custom aggregation functions https://docs.djangoproject.com/en/1.8/ref/models/expressions/#creating-your-own-aggregate-functions
  • 101. Linkodrome (page 2) 1 / n My GitHub account https://github.com/barseghyanartur django-debug-toolbar https://pypi.python.org/pypi/django-debug-toolbar
  • 103. Thank you! Artur Barseghyan Job Ganzevoort Goldmund, Wyldebeast & Wunderliebe http://www.goldmund-wyldebeast-wunderliebe.nl/

Editor's Notes

  1. Hi, Next to me is Artur Barseghyan, my name is Job Ganzevoort. We’re developers at Goldmund, Wyldebeast and Wunderliebe. We’re here to present the work we did last year to get a high-traffic e-commerce site to perform great, especially under stress. We’ll discuss overall architecture, bottlenecks, solutions and results
  2. Vacansoleil --- sponsoring today’s event --- is an e-commerce company selling camping holidays. Over 500 campings throughout Europe. Company has its head office is in the Netherlands, and sales offices in 11 countries. Domains .nl .be .fr .de .at .it .pl .dk .co.uk .ie .hu Seasonal: peak visits first 2 weeks of january, sunday evening, 21:30 600K pageviews per day, sustained load of 2K pageviews per minute for hours.
  3. iSeries ERP-ish system responsible for product, availability and transactions. Revamping architecture, piece by piece, servicebus. New website, gradually deployed to all domains from march to september 2016. Old website frontend would talk directly to iSeries. New website frontend through Django workers. AJAX heavy search mechanism. Will new website be able to handle the load? We had a hunch it wouldn't.
  4. Topic of this talk is the performance of the webserver, measured in number of page views per second that the site can handle. As a result of improving the performance, the user experience improved a lot too. During 2016 we gradually delivered the new website to all vacansoleil domains. In this new version, browsers communicate only with the webserver, not with the iSeries backend. Browser requests are handled with Apache, where statics and media are served by apache directly, dynamic requests proxied to Django running under gunicorn. Our database of choice is PostgresQL. A lot of content is retrieved from the iSeries backend.
  5. I’ll be honest: our MVP felt sluggish even without load. We started investigating bottlenecks. These included the items in this sheet. Each time we eliminated one, we’d be running into a new wall. Over time though, things improved a lot. The order in which we’re presenting here is more outside-in that chronological.
  6. We’re running in a virtualized environment so these parameters can be tuned. We’re using a single VPS. Partitioning it over several servers so far hasn’t been necessary. Processors do 5000 bogomips. Yay! We started with 8 cores. There was a time we were running with 40 cores but better caching brought the need down to about 16. We’re using 24 now which gives us plenty headroom. Allocation copious amounts of RAM to different components in the system certainly helped. OS level filesystem caching is great too. The virtualization platform allows us to scale servers up to 48 cores, 192GB.
  7. Our webserver is Apache. Out of the box, the default Multi Processing Module of apache is “prefork”, which allocates a fixed number of processes to handle requests. Allowing browsers to use keepalive blocks idle processes, severely limiting the number of concurrent users of the site. Even under a moderate load this quickly became a problem. Setup time for an http connection would be long, waiting for processes to become available. The event MPM uses a single thread to listen for incoming requests, then delegating these to worker threads, managing the total amount of worker threads, keeping that number between a predefined lower and upper bound. With the values we picked, apache was never the bottleneck during stresstests or real-life load.
  8. We started adding logging. With a high number of django workers, max_connections at 100 was not enough. Our server has more than enough RAM, so we allocated generous amounts to the database. Large shared_buffers help keeping the database in RAM, reducing accesses to the harddrive. We added logging to find slow DB queries, choosing 500ms as an arbitrary threshold. This helped us identify locking issues with the django_sessions table. As a result of this tuning, database calls were noticeably faster and especially the number of slow queries dropped.
  9. We experimented with the number of gunicorn worker processes. If this number is too low, handling of requests can stall eventually giving timeouts. Advise per gunicorn docs: start with (2 x $num_cores) + 1, we found 32 to be plenty.
  10. Typical usage of this site is anonymous so we tried to avoid sessions. We only use sessions for logged-in users, these are site admins and content editors. Storing the session in memcached instead of postgresql is faster but more volatile. If memcached doesn’t work properly (not enough memory allocated, or reboot), editors will have to login again.
  11. This is mostly about the search page, but the search widget is basically available on each page of the site.
  12. So here’s how the search widget works. The yellow block on the left is the form containing widgets for destination (country/region), period (arrival/departure/num_days_earlier_later), travel group (adults/children/pets), accommodation types (mobile home/tent/etc) and other features/facilities.
  13. The search API call returns 1: all campings and their accommodations that match the form values. That information is a plain list containing only IDs, to keep the result set small (<20KB) and fast. 2: facet counts: the number of campings matching a particular item, restricted by the other form widgets. These results are displayed 1: within the form (facet counts) 2: header 3: pagination bar 4: results area filled through second API call
  14. The current page consists of 5 search results (campings with accommodations matching the form values), rendered through searchresults API call. We tried to make this as lean as possible too. You’re seeing 1 foto per camping here, but there’s a bxslider there with about 25 fotos in it. The template code to process those 5x25 fotos took about 60% of the time in this call. We now only include 3 here, and the rest on interacting with the bxslider.
  15. So, when searching, we’re actually looking for available accommodations that match the selected criteria. Some data is rather static; for instance: campings and accommodations usually stay within the same country. The presence of a swimming pool also doesn’t change multiple times per day. Availability however does, because someone might have just booked the last remaining mobile home at a particular camping. We fetch all static searchfacts pre-emptively. Daily sync. From this, we create AccoFilterSets, for example a set for all accommodations that have airconditioning. On performing a search, we query the iSeries backend for raw availability data including only the date parameters. This yields a set of available accommodations that is then intersected with the AccoFilterSets for the other selected features to get the final search result set. In that same pass the facet counting is performed: for each possible parameter value the number of campings/accommodations you’d get if that parameter was selected, in relation to the other selected parameters. So why not elasticsearch (or solr, or whathaveyou)? 1: actually, the code isn’t too complicated, about 200 lines of code, and fast, after getting the raw availability set, all filtering and faceting is done in less that 20 milliseconds. 2: modeling this, especially the faceted search, in elasticsearch wasn’t easy. A complicating aspect is that we’re filtering on accommodations, but we’re showing the number of campings that have an accommodation with the required features.
  16. The webserver is making a lot of calls to the iSeries backend. One of the reasons we implemented search as I explained on the previous slide is that the iSeries request for raw availability is decoupled from the other search criteria, increasing the chances that the results are reusable. So we cache these, using Memcached. A user interacting with the widget, for example selecting a country/region, mobilehome with airconditioning, gets the facet counts updated rather quickly because the iSeries backend is queried only once. A second user, searching with the same arrival/departure dates will reuse the cached availability information too. Maximum cache age is a setting per API call type. Availability caching is rather short, like 10 minutes to an hour. For prices, the iSeries allows querying a batch of accommodations at once, and for rendering the search results we do. Pagination shows 5 campings at once, each with a handful of accommodations. In this case, we cache the individual accommodation prices and not the entire batch.
  17. This wasn’t “just add varnish, and poof…”, we had to adapt the site to be varnish friendly. We also made varnish quite aggressive. An early observation was that we have many anonymous, few authorized visitors. We took extra steps to separate these authorized visitors, who are site admins and content editors. These use a separate URL which is uncached, limited to local network (and VPN) only. The public site URL is available only for anonymous users, and cacheable. On GET requests, varnish simply strips all cookies, on the response too. Most GET query parameters (for example google analytics campaign parameters) are handled in javascript, so removed from the query. Django doesn’t need to see those. Structuring pages / AJAX calls to be as independent / cacheable as possible. Shopping cart (suitcase icon topright) Csrftoken Overall 40% hitrate. Highest on (cms) pages which is great, because those are expensive. Lower on search API because wide variety in possible search parameters. But those are pretty fast due to reuse of partial search results.
  18. We’re halfway our presentation now, and I’d like to summarise the approach so far We’ve looked at the full stack: server, database, apache configuration. Varnish reverse proxy caching, for which we adapted the site to make varnish really effective. We designed the search APIs to be simple, fast, cacheable. Smart search mechanism. Reusing queries to the iSeries backend. But there’s more Artur will take you on a tour of Django and DjangoCMS-specific optimizations, focussing on the ORM.
  19. Django optimisations Optimisations take time. It may seem endless. And in most of the cases you won’t reach the limits of “no optimisations further possible”. But, there are a couple of key factors to consider.
  20. But how to measure performance? Profiling tools. Django has some.
  21. django-debug-toolbar Just make sure you don’t accidentally enable it on staging or production.
  22. Surprised?
  23. Just joking
  24. django-debug-toolbar-template-profiler OK, another one. One of the best add-ons ever. Just pip install, add extra panel to INSTALLED_APPS and DEBUG_TOOLBAR_PANELS. I discovered it about 2 years ago. Be careful, though. It affects the page load speed drastically.
  25. Before we move on django-debug-toolbar is an excellent tool, but page rendering timings lie. django-debug-toolbar needs to render a lot of HTML as well. The more database/cache/etc information to show, the longer page loads you get. As I mentioned, some panels affect the page load speed drastically. Don’t be scared, though. In production (or with DEBUG set to False) things aren’t that slow. However, it’s not an excuse to avoid optimisation. When you’re outside the request/response cycle, use logging to get an idea of what’s happening to your system.
  26. Let’s start with imaginary app... Just a couple of models. And views. They aren’t anyhow related to the Vacansoleil. Still are based on issues and use cases I have experienced there.
  27. Imaginary app (page 1) Pubisher, Author
  28. Imaginary app (page 2) Book model with many-to-many relation to Author and foreign key relation to Publisher.
  29. Imaginary app (page 3) Do I need this?
  30. ORM & Database queries To name a couple of tips… Make use of `select_related` and `prefetch_related` for fetching related objects at once. Make use of `only` and `defer` (typical use case: listing views), but use them with caution. Use `annotate` and `aggregate` for calculations on database level. When fetching IDs of related item (if you only need ID), use {field_name}_id instead of {field_name}.id, since the first one does not cause table JOIN. Consider using `values` or `values_list` for listing views, when possible.
  31. Use case Listing of books (about 2000 in total) with the following information: Book title Names of all authors of the book separated by comma Publisher name Number of pages Price Our sample use case - just have a simple book listing Show title Show names of all authors of the book (remember, many-to-many relation) separated by comma Show publisher name (remember, foreign key relation) Show number of pages (same model/table) Show price (same model/table)
  32. Bad example A bad way of fetching information.
  33. Results 4023 queries took 730 ms, page rendered in 23932 ms As a result, about 4000 queries, slow page loads. Sad, isn’t it?
  34. Let’s improve Let’s improve… We will use: select_related for dealing with foreign key relations. prefetch_related for dealing with many-to-many relations. only for fetching desired columns only.
  35. Good example Just putting things together.
  36. Results 2 queries took 12 ms, page rendered in 2104 ms Only 2 database hits, much faster page load. Surprised?
  37. Let’s improve more Let’s improve further! We’re gonna add: values annotate
  38. Even better example This is how it would look. Note, that no prefetch_related, select_related or only is needed here. Values will take care of it.
  39. Results 1 query took 13 ms, page rendered in 568 ms One query left, page rendered 4 times faster than in improved example. Nice, isn’t it?
  40. But what is GroupConcat? Some of you might ask… But what is a GroupConcat? Shall I go 2 pages back? OK.
  41. Custom aggregation functions Django makes it very easy to write custom aggregation functions. There are a lot of bundled ones, such as Sum, Avg, Min, Max, etc. If you use Postgres, make sure to check a dedicated contrib package (contrib/postgres/aggregates). In case of Postgres you should probably be using “StringAgg”. But for now - it’s just our tiny custom aggregation function, which saved us a lot of page rendering time.
  42. Surprised?
  43. Avoid database hits in the loop! I’m gonna repeat it more than once today.
  44. Use case Our next example. Create 50 records.
  45. Bad example A bad way to do it.
  46. Results 98 queries took 40 ms, page rendered in 652 ms Sad, isn’t it?
  47. Let’s improve Let’s improve! Use bulk_create.
  48. Good example Here 50 objects are inserted at once. Of course you should be using factories, but it’s out of the scope of this presentation!
  49. Results 2 queries took 1.73 ms, page rendered in 60 ms That’s a lot times faster! Nice, isn’t it? Surprised?
  50. If you use django-debug-toolbar already... Those of you who worked with django-debug-toolbar may want to say...
  51. I know it, django-debug-toolbar is great... “What the hell is this guy talking about? It’s a well known thing!” “Say something new! We use DDT for at least 5 years already”. While other would say: “Yeah, it’s nice, but it doesn’t handle the AJAX requests and no JSON responses at all...” However...
  52. django-debug-toolbar-force It’s possible! with django-debug-toolbar-foce A single pip install, tiny additions to your configuration (MIDDLEWARE_CLASSES or MIDDLEWARE) and… You can debug AJAX views and JSON responses just the same way, almost. Just add ?debug-toolbar to a URL of any AJAX or JSON view.
  53. Surprised? You didn’t know about it, did you? :)
  54. Hey, enough with these faces! You may think, “hey, stop showing stupid pictures”.
  55. Fine. Let’s move on... OK, let’s go on.
  56. One important thing! One important thing As I already mentioned, I’ll be saying it more than once today...
  57. Avoid querying in the loop! Don’t make queries in the loop. No matter what kind of queries, database, cache, elastic, whatever… Just don’t. Move all the queries outside the loop. Fetch at once and reuse the result set later (in the loop). Insert/update/delete in a batch.
  58. Given the following model... Another nasty example. And the page_id is the correspondent ID of the CMS page.
  59. But WHY??? You may think... “Who writes such things?” or “No, it never happens in practice”
  60. Because it happens and you have to deal with it! But it does. And you have to deal with it. You want an example? A serious example of a Django app where such things happen? OK, you asked for it - DjangoCMS! Pretty serious, isn’t it?
  61. Surprised? Did you know about it? Surprised?
  62. Bad example OK, let’s see how we can deal with that. A bad way of doing that would be… doing that in a loop! Imagine, you have 100 items...
  63. Results 101 database queres Sad, isn’t it?
  64. Improved example Let’s improve! We can’t use select_related or prefetch_related here. But we can grab data at once, and do necessary joins in Python.
  65. Results No additional database queries and no missed cache hits. A little bit of code, but it’s worth it, right? Surprised?
  66. Unnecessary cache hits ‘Now some things about the cache. And I’m gonna talk about Django cache (Memcached, Locmem). Don’t mix it with Varnish. Django's cache can dramatically improve your site performance, but misuse can lead to slow-downs. First of all, analyse. Use django-debug-toolbar to track down what the cause is. It many cases it would be possible to refactor the slow part. Use template fragment caching when possible. If you have more than 500 repetitive cache hits on a page, something isn’t right. Identify heavy parts of your template with TemplateProfilerPanel. Try to optimise heavy parts and after that, if it can be done, cache those heavy parts (for at least 10 minutes). Document in the code why you cache certain parts. Keep track of all cache usages in a separate section of your developer documentation.
  67. Optimise templates Clean up your templates. Remove unused imports. Try to avoid queries in the loop. If you work with DjangoCMS beware, that any {% page_url %} or {% page_attribute %} will make a lot of queries in the loop.
  68. A couple of things Reduced number of queries often leads to performance boost… ...still, reduced number of queries does not necessarily mean that your database is used efficiently. Use EXPLAIN statements (provided by the django-debug-toolbar) to identify the heavy parts. Fine-tune your queries. Add indexes when necessary. In development, run two instances of the project. One with the change, another without. Always compare the "before" and "after" optimisation. Test page load speed with caching on and off. Test the first page load and the second page load.
  69. Tips Avoid use of content processors and middleware that do make database (or cache) hits. If you need to have some data in templates, use templatetags on specific templates to fetch it. In any case, if you absolutely have to use it, make sure it's lazy (unless impossible by-design)
  70. DjangoCMS specific tricks Some tricks specific to DjangoCMS.
  71. DjangoCMS is fine... One thing you should know is that Once you have integrated DjangoCMS in your site all your vanilla, non-CMS related views will become heavier. There will be additional checks and queries fired by CMS middleware and CMS context processors. It may seem weird and irrelevant, but that’s how it is.
  72. Scary? Sad isn’t it?
  73. Hold on, there’s a way to fix it! But wait, there’s a fix for it!
  74. Now you non-CMS views are clean ...or almost clean. And if they aren’t yet, try to find out how to optimise that.
  75. Surprised?
  76. Dealing with {% page_url %} Try to avoid: Usage of {% page_url %} or {% page_attribute %} tags on IDs. Instead, pass complete objects only. Apply some tricks to the querysets to fetch the page objects efficiently. If you list a lot of DjangoCMS Page objects (with URLs) on a page, don’t use {% page_url %} at all. Instead, use customised template tag which fits your needs better.
  77. Use case Or next use case. A sitemap with about 200 CMS pages.
  78. Bad example A bad example.
  79. Results 2000 database queries and 3000 missed cached hits Produces a way too many database queries and missed cache hits. Sad, isn’t it?
  80. Improved example (page 1) The code I’m going to show isn’t that simple and obvious. It’s what worked well in the project I worked on. You should not be using this everywhere. However if you know what you're doing, it can save you a lot of page rendering time. You might want to modify it to fit your needs better. The slides will be available on the slideshare, you won’t miss the clue. For now, let’s move on.
  81. Improved example (page 2) Sorry, it didn’t fit on one page.
  82. Improved example (page 3) It didn’t fit even on two pages...
  83. Improved example (page 4) Almost there...
  84. Improved example (page 5) Yes. That’s it.
  85. Results As a result - no additional database queries and no missed cache hits. That’s a lot of custom code, but it’s worth it.
  86. Surprised? Surprised?
  87. Use get_render_queryset on CMS plugins The CMS plugins. Honestly, there are parts (container plugins), which I didn’t optimise due to lack of time. Maybe I will, one day. But, let’s talk about parts that are possible to optimise. Each plugin has a class method get_render_queryset. Use it to fetch objects in a more efficient way.
  88. Use case Our use case, a simple plugin, which has 2 foreign key relations.
  89. Bad example A bad example.
  90. Results As a result, 3 database queries for rendering of a single plugin. And how many plugins do you have on a page?
  91. Good example Let’s improve! select_related is our friend!
  92. Results Yep, just 1 query for rendering of a single plugin. Surprised?
  93. Load testing OK, the very last part and I’m afraid I won’t have much time for it. The load testing.
  94. Apache JMeter After a small research, we have picked Apache JMeter for a number of reasons. Load/stress testing tool for analyzing/measuring the performance. Focused on web applications. Plugin based architecture. Supports variable parametrisation, assertions (response validation), per-thread cookies, configuration variables. Comes with a large variety of reports (aggregations, graphs). Can replay access logs, including Apache2, Nginx, Tomcat and more. You can save your tests and repeat them later.
  95. We measured “before”... Before the changes (optimisation) were deployed, we have measured the performance.
  96. We measured “after”... We have measured it again, after deploying the performance optimisation changes.
  97. And “after” was a way better! And after was a way better!
  98. We’re happy We’re happy.
  99. To name a couple of links.
  100. To name a couple of links.