After taking serious time and effort, you have built your own Django application. What if the website takes too much performance overhead and gets too slow after some reasonable good amount of traffic? There are a couple of features and methods for optimizing the code and improving the overall user experience.

The primary step is to learn about the basic optimizations that are included in Django’s documentation just because it’s all thoroughly described inside the docs.

1. SQL optimizations

It’s important to avoid the use of redundant code which makes excessive Django ORM queries. Avoid the use of repetitive evaluation of queries which makes repetitive database hits. Similarly, the use of double evaluation of big queries should be avoided.

class Product(models.Model):
def count_similar_products(self):
return Product.objects.filter(type=self.type).count()

Here product.count_similar_products will make the double evaluation.

Also, the use of Django Debug Toolbar helps a lot to monitor the SQL queries being executed.

2. Caching

Caching is the process of keeping often requested objects ‘close’ so that they can be delivered really fast. Computers and the internet would crawl and die without the use of caching. Cache are implemented in almost all components — processors, disks, operating system, DNS, databases, browsers, etc.

Before using Django’s cache framework, determine the pages that are most often served and concentrate primarily on those.

There are different cache servers which can be used in Django. Memcache is the most efficient one. To use Memcache with Django, define it in settings as:

CACHES = 
'default':
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',

Per view cache
 A whole Django view can be cached with cache_page. Thecache_pagedecorator will cache the response returned from the view.

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def index(request):
return render(request, 'index.html')

cached_property
 Using the cached_property decorator saves the value returned. During the next time the function is called on that instance, it will return the saved value.

from django.utils.functional import cached_property

@cached_property
class Product(models.Model):
def count_similar_products(self):
return Product.objects.filter(type=self.type).count()

select_related() and prefetch_related()
 Hitting the database multiple times for different parts of a single ‘set’ of data is, in general, less efficient than retrieving it all in one query.
 select_related() results in a single more complex query but the later use of foreign-key relationships won’t require database queries.

# Hits the database.
e = Entry.objects.get(id=5)
# Hits the database again to get the related Blog object.
b = e.blog

Using select_related():

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)
# Doesn't hit the database, because e.blog has been prepopulated in the previous query.
b = e.blog

While select_related() offers you advantage on foreign-key relationships, prefetch_relatedcan be used for ManyToManyField and their relationships.

Template Fragments
 You can cache template fragments using the cache template tag. To give your template access to this tag, add % cache % template tag caches the contents of the block for a given amount of time. It takes at least two arguments: the cache timeout (in seconds), and the name to give the cache fragment.

% load cache %
% cache 600 test_fragment %
% trans "SayOne Technologies" %
% endcache %

Session Caching
 Session caching eliminates the need to load session data from sources like database, and instead stores frequently used session data in memory.

SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer'
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

Object Caching
 At times, caching an entire rendered page doesn’t gain you very much. In fact, it incurs more overhead. Your site may include a view whose results depend on several complex queries. You wouldn’t want to cache the entire result, but just the values which rarely change. In this case, it would not be ideal to use the full-page caching that the per-site or per-view cache offer. To tackle this case, Django uses a low-level cache API to store objects in the cache.

from django.core.cache import cache

cache.set(‘Python’, ‘Django’, timeout=600)
cache.get(‘Python’)
cache.delete(‘Python’)
cache.set_many(‘FOO1’:2015,’FOO2’:1997)
cache.get_many([‘FOO1’,’FOO2’])

Caching outside Django
 Caching can also be done using other strategies like web servers, reverse proxies, and CDNs (Content Delivery Networks) like CloudFlare.

3. Avoid user waiting time

Other useful techniques that can be adopted for optimization include:
 — Scheduling the execution of events like confirmation emails.
 — Process any requests that are guaranteed to time-out, using asynchronous methods.
 — Use of Celery / Gevent for scheduling.
 — Use of template engines like Jinja2 for faster rendering. Django 1.8 provides built-in support for Jinja2.

In addition, don’t forget to log all the database queries. Close monitoring of these logs will help you to identity the queries taking too much time, and work around until these queries become faster. Connecting more servers to the architecture can also come in handy, for reduced load times.