Showing posts with label django-cms. Show all posts
Showing posts with label django-cms. Show all posts

Jan 2, 2010

Response time optimisation with Varnish


This blog post shows you how to optimize the tools chain on your server to improve its performance by an order of magnetude with out changing a single line in your django project. In order to do so I will use again django-cms [1] as guinea pig because there is a fair amount for processing to display a page but it is still easy to install. Note: django-cms example has the cache middleware activated by default.


Then I will run ab testing on a particular page and compare the results. These tests are being performed on my laptop hp dv6-1030. The important information is not the figures but by them self but rather the variation of the response time.



Before starting my test I have moved django-cms to be mounted under "/". In order to do this you will need to change the configuration into the file called example_uwsgi.py.


import os
import django.core.handlers.wsgi

# Set the django settings and define the wsgi app
os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings'
application = django.core.handlers.wsgi.WSGIHandler()

# Mount the application to the url
applications = {'/':application, }



Then you need to change the rule behavior in cherokee admin to reflect this change. Cheorkee admin makes this task a breeze.









Before diving head first into the the meat of this article here it is a diagram of the architecture that we are going to work with :





The goal of this article is to show you the incredible boost that varnish can give to certain type of web application. 



varnish [2] is a state-of-the-art, high-performance HTTP accelerator. It uses the advanced features in Linux 2.6, FreeBSD 6/7 and Solaris 10 to achieve its high performance.
Some of the features include:
  • VCL - a very flexible configuration language
  • Load balancing with health checking of backends
  • Partial support for ESI
  • URL rewriting
  • Graceful handling of "dead" backends
  • ...

The bottom line is that just by installing it and using it with a vanilla configuration, on ubuntu, will increase the responsiveness of your site by an order of magnitude that is hard to believe we are talking here of an improvement factor ranging from 50 to 600 times.



The first thing that you would like to do is to install varnish [2]. On ubuntu varnish is very easy to install/configure since there is a package that exists. Once this operation is executed you will need to define the backend, this varnish jargon means that you need to tell varnish where Cherokee is located.


backend default {
.host = "127.0.0.1";
.port = "8080";
}



Here it is some ab tests that I have done to illustrate this article, 8080 and 6081 are respectively the port for Cherokee and Varnish.

Cherokee

ab -n 100 -c 1 http://192.168.1.18:8080/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.18 (be patient).....done


Server Software: Cherokee/0.99.37
Server Hostname: 192.168.1.18
Server Port: 8080

Document Path: /
Document Length: 3440 bytes

Concurrency Level: 1
Time taken for tests: 15.285 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 364300 bytes
HTML transferred: 344000 bytes
Requests per second: 6.54 [#/sec] (mean)
Time per request: 152.851 [ms] (mean)
Time per request: 152.851 [ms] (mean, across all concurrent requests)
Transfer rate: 23.28 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 2.4 0 24
Processing: 133 153 17.3 149 235
Waiting: 133 153 17.3 149 235
Total: 134 153 17.2 149 235

Percentage of the requests served within a certain time (ms)
50% 149
66% 158
75% 164
80% 166
90% 172
95% 175
98% 230
99% 235
100% 235 (longest request)


ab -n 100 -c 50 http://192.168.1.18:8080/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.18 (be patient).....done


Server Software: Cherokee/0.99.37
Server Hostname: 192.168.1.18
Server Port: 8080

Document Path: /
Document Length: 3440 bytes

Concurrency Level: 50
Time taken for tests: 8.202 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 364300 bytes
HTML transferred: 344000 bytes
Requests per second: 12.19 [#/sec] (mean)
Time per request: 4101.021 [ms] (mean)
Time per request: 82.020 [ms] (mean, across all concurrent requests)
Transfer rate: 43.37 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.1 1 3
Processing: 740 3283 1158.0 3906 4438
Waiting: 740 3283 1158.0 3906 4438
Total: 743 3284 1157.1 3906 4438

Percentage of the requests served within a certain time (ms)
50% 3906
66% 4048
75% 4112
80% 4182
90% 4285
95% 4341
98% 4359
99% 4438
100% 4438 (longest request)

ab -n 100 -c 100 http://192.168.1.18:8080/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.18 (be patient).....done


Server Software: Cherokee/0.99.37
Server Hostname: 192.168.1.18
Server Port: 8080

Document Path: /
Document Length: 3440 bytes

Concurrency Level: 100
Time taken for tests: 8.236 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 364300 bytes
HTML transferred: 344000 bytes
Requests per second: 12.14 [#/sec] (mean)
Time per request: 8235.626 [ms] (mean)
Time per request: 82.356 [ms] (mean, across all concurrent requests)
Transfer rate: 43.20 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 3 4 0.8 4 5
Processing: 699 4533 2337.0 4650 8228
Waiting: 699 4533 2337.0 4650 8228
Total: 704 4537 2336.2 4654 8230

Percentage of the requests served within a certain time (ms)
50% 4654
66% 5884
75% 6717
80% 7017
90% 7749
95% 8115
98% 8221
99% 8230
100% 8230 (longest request)

Varnish


ab -n 100 -c 1 http://192.168.1.18:6081/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.18 (be patient).....done


Server Software: Cherokee/0.99.37
Server Hostname: 192.168.1.18
Server Port: 6081

Document Path: /
Document Length: 3440 bytes

Concurrency Level: 1
Time taken for tests: 0.030 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 374800 bytes
HTML transferred: 344000 bytes
Requests per second: 3320.49 [#/sec] (mean)
Time per request: 0.301 [ms] (mean)
Time per request: 0.301 [ms] (mean, across all concurrent requests)
Transfer rate: 12153.53 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 0 0 0.9 0 8
Waiting: 0 0 0.9 0 8
Total: 0 0 0.9 0 8

Percentage of the requests served within a certain time (ms)
50% 0
66% 0
75% 0
80% 0
90% 0
95% 0
98% 4
99% 8
100% 8 (longest request)

ab -n 100 -c 50 http://192.168.1.18:6081/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.18 (be patient).....done


Server Software: Cherokee/0.99.37
Server Hostname: 192.168.1.18
Server Port: 6081

Document Path: /
Document Length: 3440 bytes

Concurrency Level: 50
Time taken for tests: 0.012 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 378548 bytes
HTML transferred: 347440 bytes
Requests per second: 8522.97 [#/sec] (mean)
Time per request: 5.866 [ms] (mean)
Time per request: 0.117 [ms] (mean, across all concurrent requests)
Transfer rate: 31507.35 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.2 0 3
Processing: 0 3 2.0 3 8
Waiting: 0 3 2.0 2 8
Total: 0 4 2.8 4 11

Percentage of the requests served within a certain time (ms)
50% 4
66% 5
75% 6
80% 7
90% 9
95% 10
98% 11
99% 11
100% 11 (longest request)

ab -n 100 -c 100 http://192.168.1.18:6081/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.18 (be patient).....done


Server Software: Cherokee/0.99.37
Server Hostname: 192.168.1.18
Server Port: 6081

Document Path: /
Document Length: 3440 bytes

Concurrency Level: 100
Time taken for tests: 0.013 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 374800 bytes
HTML transferred: 344000 bytes
Requests per second: 7662.25 [#/sec] (mean)
Time per request: 13.051 [ms] (mean)
Time per request: 0.131 [ms] (mean, across all concurrent requests)
Transfer rate: 28045.03 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 2 3 0.6 3 4
Processing: 7 7 0.7 7 9
Waiting: 5 6 0.6 6 7
Total: 9 10 1.2 10 13

Percentage of the requests served within a certain time (ms)
50% 10
66% 10
75% 10
80% 11
90% 12
95% 13
98% 13
99% 13
100% 13 (longest request)

Conclusion

Installing varnish in front of your web server is propably this first step you should take in the end less journey of optimising your web application. It is interesting to note that in addition of dramatically improving the response time Varnish will also reduce the load on your application server stack [ uWSGI + django +db].



This blog post barely scratches the surface of how django can take advantage of of caches, django gives you the possibility to cache information at different stages during the request/response cycle. You can cache the output of specific views, you can cache only the pieces that are difficult to produce, you can cache a portion of template, or you can cache your entire site. Django also works well with "upstream" caches, such as varnish and browser-based caches. These are the types of caches that you don't directly control but to which you can provide hints (via HTTP headers) about which parts of your site should be cached, and how. If you want more information about this you can read the django's cache documentation.

Varnish is also a beast by itself, you can fine tuned it to suit your particular situation and you can used it to do much more in your infrastructure than just upstream cache of your dynamic web site.





[1] http://yml-blog.blogspot.com/2009/12/flup-vs-uwsgi-with-cherokee.html
[2] http://varnish.projects.linpro.no/
[3] http://docs.djangoproject.com/en/1.1/topics/cache/#topics-cache

Dec 14, 2009

Paginated feed for cmsplugin_feed

In my previous post [1] I wrote about my experience of writing a new plugins for django-cms [2]. This plugin gives you  the capability to add a feed to a django-cms' Page.

I ended my post by asking about the best practice used to paginate the content of a plugin. Since I didn't get flooded by the answers I assumed that this shouldn't be different than doing it on a django app. Here it is what the fine django documentation says about this topic.

So I have decided to implement this approach, the key point here is to understand that django-cms' plugin has access to the context :


class FeedPlugin(CMSPluginBase):
    [...]
    def render(self, context, instance, placeholder):
        feed = get_cached_feed(instance)
        if instance.paginate_by:
            is_paginated =True
            request = context['request']
        [...]

This capability lets your plugin react to the GET and POST parameters. Once I have understood this the only thing that I had to do is to use the django's Paginator on the list of feed entries.

As you can see in the code below there is nothing specific to django-cms there.

            is_paginated =True
            request = context['request']
            feed_page_param = "feed_%s_page" %str(instance.id)

            feed_paginator = Paginator(feed["entries"], instance.paginate_by) 
            # Make sure page request is an int. If not, deliver first page.
            try:
                page = int(request.GET.get(feed_page_param, '1'))
            except ValueError:
                page = 1
            # If page request (9999) is out of range, deliver last page of results.
            try:
                entries = feed_paginator.page(page)
            except (EmptyPage, InvalidPage):
                entries = feed_paginator.page(paginator.num_pages)

The complete implementation of this feature is available in the bitbucket repository of cmsplugin_feed.

I would be glad to hear from you if this implementation could be enhanced.

[1] http://yml-blog.blogspot.com/2009/12/feed-extension-for-django-cms.html
[2] http://www.django-cms.org/
[3] http://docs.djangoproject.com/en/dev/topics/pagination/
[4] http://bitbucket.org/yml/cmsplugin-feed/changeset/7d0644c7668f/

Dec 12, 2009

feed extension for django-cms

I have been lately looking at the open source CMS alternatives based on django. After some times investigating I have decided to give django-cms [1] a try.

django-cms provides an API to write plugin it is well documented [2] and you can also look the implementation of plugins listed there [3]. I have decided to experiment with it by writing a plugin that displays the content of a feed into a page. The source code is available on my bitbucket account in a project called cmsplugin-feed. The development of this plugin has been relatively straight forward and once you get to know the conventions defined to implement an extension. You feel like you are writing python code for a django application. The CMS part does not stand on your way at least it didn't in my experimentation.

Here it is 2 screenshots showing this plugin in action :
In order to add a feed you need give a name and an URL to your feed



Then the plugin displays the field on a page.




I would be interested to read from someone the "best practice" to paginate the content of a plugin. Ideally I would like to add a parameter for every feed indicating how many items I want per page. Then I would like to paginate the feed in the placeholder of every page and this will allow the user to navigate between the pages.

[1] http://www.django-cms.org
[2] http://www.django-cms.org/en/documentation/2.0/custom_plugins/
[3] http://www.django-cms.org/en/extensions/
[4] http://bitbucket.org/yml/cmsplugin-feed/