<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
		>
<channel>
	<title>Comments on: Django performance testing &#8211; a real world example</title>
	<atom:link href="http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/feed/" rel="self" type="application/rss+xml" />
	<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/</link>
	<description>Code and comments on web development, Django, Python and things (un)related.</description>
	<lastBuildDate>Tue, 13 Jul 2010 16:31:27 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
		<item>
		<title>By: Jan Paricka</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-770</link>
		<dc:creator>Jan Paricka</dc:creator>
		<pubDate>Wed, 27 Jan 2010 12:36:33 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-770</guid>
		<description>Oh, boy.  Easily half a gig per httpd process..  Anyone have seen this before?  http://twitpic.com/zzem4  Great article, btw.  Thanks for putting it together.</description>
		<content:encoded><![CDATA[<p>Oh, boy.  Easily half a gig per httpd process..  Anyone have seen this before?  <a href="http://twitpic.com/zzem4" rel="nofollow">http://twitpic.com/zzem4</a>  Great article, btw.  Thanks for putting it together.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Scott</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-630</link>
		<dc:creator>Scott</dc:creator>
		<pubDate>Sat, 17 Jan 2009 22:06:51 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-630</guid>
		<description>@SJS

Sorry, I don&#039;t have any experience using this.  The authentication I used was faked by setting the cookie header to a known value.  In your case, httperf is handling sessions by itself - receiving a cookie and sending it on subsequent requests.</description>
		<content:encoded><![CDATA[<p>@SJS</p>
<p>Sorry, I don&#8217;t have any experience using this.  The authentication I used was faked by setting the cookie header to a known value.  In your case, httperf is handling sessions by itself &#8211; receiving a cookie and sending it on subsequent requests.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: SJS</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-627</link>
		<dc:creator>SJS</dc:creator>
		<pubDate>Fri, 16 Jan 2009 02:26:08 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-627</guid>
		<description>Could you please explain bit more about &#039;authenticated requests with httperf&#039;. I am wondering about the format of my session file with the POST data. The site I am trying to log in have a one cookie. Two different urls in the same session ends us receiving two different vales for the cookie though I use --session-cookie with --wsesslog option.Thx</description>
		<content:encoded><![CDATA[<p>Could you please explain bit more about &#8216;authenticated requests with httperf&#8217;. I am wondering about the format of my session file with the POST data. The site I am trying to log in have a one cookie. Two different urls in the same session ends us receiving two different vales for the cookie though I use &#8211;session-cookie with &#8211;wsesslog option.Thx</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Sephi</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-419</link>
		<dc:creator>Sephi</dc:creator>
		<pubDate>Mon, 08 Sep 2008 09:18:03 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-419</guid>
		<description>First check if you can reduce the number of queries executed (using this snippet for example : http://www.djangosnippets.org/snippets/93/), then profile them using the SQL EXPLAIN statement (not sure if it&#039;s a standard one though, but it works on MySQL). With this, you&#039;ll be able to identify &quot;greedy&quot; queries and try to optimize them by, for example, setting indexes on the right columns. 

And if your indexes are already good, cache the objects for which queries are the most expensive. You could also check your my.cnf values and try to tune it, but 256Mb of RAM is a bit short...

Nice article though !</description>
		<content:encoded><![CDATA[<p>First check if you can reduce the number of queries executed (using this snippet for example : <a href="http://www.djangosnippets.org/snippets/93/)" rel="nofollow">http://www.djangosnippets.org/snippets/93/)</a>, then profile them using the SQL EXPLAIN statement (not sure if it&#8217;s a standard one though, but it works on MySQL). With this, you&#8217;ll be able to identify &#8220;greedy&#8221; queries and try to optimize them by, for example, setting indexes on the right columns. </p>
<p>And if your indexes are already good, cache the objects for which queries are the most expensive. You could also check your my.cnf values and try to tune it, but 256Mb of RAM is a bit short&#8230;</p>
<p>Nice article though !</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Scott</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-194</link>
		<dc:creator>Scott</dc:creator>
		<pubDate>Tue, 29 Apr 2008 15:00:55 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-194</guid>
		<description>@S.

Thanks for the suggestion to disable keep-alive for the dynamic content.  I tried it, but it seems to have made no difference.

The &lt;a href=&quot;http://trac.lighttpd.net/trac/wiki/Docs:Performance#http-keep-alive&quot; rel=&quot;nofollow&quot;&gt;Lighttpd docs&lt;/a&gt; refer to disabling keep-alive as &quot;the last resort&quot;.  Reducing the requests and idle time from the defaults didn&#039;t seem to make a difference either.

@jeo

We&#039;re old SQL hounds, so we know all about joins and unions. :)

A wall has multiple items.  Each item is sent by a user and can have several pieces of media (e.g. photos).

The initial/naive approach was one query (through normal Django ORM) for the wall then in the template something like:


{% for item in wall.items.all %}
    ...
    {% with item.sender as sender %}
        {{ sender.name }} etc...
    {% endwith %}
    ...
    {% for media in item.media.all %}
        ...
    {% endfor %}
    ...
{% endfor %}


I would say this is pretty standard -- it&#039;s the easy, natural way to work with the ORM.  Of course, it means we are doing one query for the items and then two queries for each item, which is bad news.  In some cases, &lt;code&gt;&lt;a href=&quot;http://www.djangoproject.com/documentation/db-api/#select-related&quot; rel=&quot;nofollow&quot;&gt;select_related()&lt;/a&gt;&lt;/code&gt; will help, but not in this case as we had some nullable foreign keys.

This can be done far more efficiently, as you say, by joining the tables, or doing a couple of queries and stitching the data together in Python.  That&#039;s what we did during optimisation.

@Vance

The point of &lt;code&gt;newforms&lt;/code&gt; and ORM and all this stuff is to make it easier/quicker to develop and maintain.  There&#039;s obviously a big performance trade-off.  I totally agree with you that Django makes it easy to drop down a level when you want to trade ease for performance.

@Doug

Thanks for sharing some numbers for the PyCon website.  What&#039;s the hardware spec of the server?

Hundreds of req/s is certainly more appealing.  We are using &lt;code&gt;values()&lt;/code&gt; now to avoid lots of queries, but no caching yet.

When you benchmarked at 800 req/s, did your test make requests as multiple users?  It occurs to me that caching could be misleading when testing performance since one user making 800 requests is very different from 800 users making one request each!

I might have a play with &lt;code&gt;mod_wsgi&lt;/code&gt; at some point -- looks interesting.</description>
		<content:encoded><![CDATA[<p>@S.</p>
<p>Thanks for the suggestion to disable keep-alive for the dynamic content.  I tried it, but it seems to have made no difference.</p>
<p>The <a href="http://trac.lighttpd.net/trac/wiki/Docs:Performance#http-keep-alive" rel="nofollow">Lighttpd docs</a> refer to disabling keep-alive as &#8220;the last resort&#8221;.  Reducing the requests and idle time from the defaults didn&#8217;t seem to make a difference either.</p>
<p>@jeo</p>
<p>We&#8217;re old SQL hounds, so we know all about joins and unions. :)</p>
<p>A wall has multiple items.  Each item is sent by a user and can have several pieces of media (e.g. photos).</p>
<p>The initial/naive approach was one query (through normal Django ORM) for the wall then in the template something like:</p>
<p>{% for item in wall.items.all %}<br />
    &#8230;<br />
    {% with item.sender as sender %}<br />
        {{ sender.name }} etc&#8230;<br />
    {% endwith %}<br />
    &#8230;<br />
    {% for media in item.media.all %}<br />
        &#8230;<br />
    {% endfor %}<br />
    &#8230;<br />
{% endfor %}</p>
<p>I would say this is pretty standard &#8212; it&#8217;s the easy, natural way to work with the ORM.  Of course, it means we are doing one query for the items and then two queries for each item, which is bad news.  In some cases, <code><a href="http://www.djangoproject.com/documentation/db-api/#select-related" rel="nofollow">select_related()</a></code> will help, but not in this case as we had some nullable foreign keys.</p>
<p>This can be done far more efficiently, as you say, by joining the tables, or doing a couple of queries and stitching the data together in Python.  That&#8217;s what we did during optimisation.</p>
<p>@Vance</p>
<p>The point of <code>newforms</code> and ORM and all this stuff is to make it easier/quicker to develop and maintain.  There&#8217;s obviously a big performance trade-off.  I totally agree with you that Django makes it easy to drop down a level when you want to trade ease for performance.</p>
<p>@Doug</p>
<p>Thanks for sharing some numbers for the PyCon website.  What&#8217;s the hardware spec of the server?</p>
<p>Hundreds of req/s is certainly more appealing.  We are using <code>values()</code> now to avoid lots of queries, but no caching yet.</p>
<p>When you benchmarked at 800 req/s, did your test make requests as multiple users?  It occurs to me that caching could be misleading when testing performance since one user making 800 requests is very different from 800 users making one request each!</p>
<p>I might have a play with <code>mod_wsgi</code> at some point &#8212; looks interesting.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: sean</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-193</link>
		<dc:creator>sean</dc:creator>
		<pubDate>Tue, 29 Apr 2008 04:33:46 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-193</guid>
		<description>i&#039;ve done some similar test around Django, and the database ORM seems to be the bottle neck to me...</description>
		<content:encoded><![CDATA[<p>i&#8217;ve done some similar test around Django, and the database ORM seems to be the bottle neck to me&#8230;</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Doug Napoleone</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-192</link>
		<dc:creator>Doug Napoleone</dc:creator>
		<pubDate>Tue, 29 Apr 2008 04:15:06 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-192</guid>
		<description>Wow, that is bad performance even after tuning.
the PyCon website can handle about 800 requests per second (all highly personalized w/ login and custom navbar). Sounds like you are not using select_related, values, and sub template caching.

I also recommend using mod_wsgi in deamon mode instead of fastcgi, using file based session caching, and memcached.

looking at the site, I see no reason why you should not be able to do 1000 requests per second.</description>
		<content:encoded><![CDATA[<p>Wow, that is bad performance even after tuning.<br />
the PyCon website can handle about 800 requests per second (all highly personalized w/ login and custom navbar). Sounds like you are not using select_related, values, and sub template caching.</p>
<p>I also recommend using mod_wsgi in deamon mode instead of fastcgi, using file based session caching, and memcached.</p>
<p>looking at the site, I see no reason why you should not be able to do 1000 requests per second.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Joe</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-190</link>
		<dc:creator>Joe</dc:creator>
		<pubDate>Tue, 29 Apr 2008 02:25:03 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-190</guid>
		<description>Really nice article walking through the performance evaluation of your site! Nice work, and thanks for posting it so others can do the same for themselves!</description>
		<content:encoded><![CDATA[<p>Really nice article walking through the performance evaluation of your site! Nice work, and thanks for posting it so others can do the same for themselves!</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Vance Dubberly</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-189</link>
		<dc:creator>Vance Dubberly</dc:creator>
		<pubDate>Mon, 28 Apr 2008 23:15:31 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-189</guid>
		<description>That is insanely bad performance, though ultimately it may be &quot;Good&quot; enough.  I often think people don&#039;t realize the trade offs they make when using frameworks like Django and Rails.   Resource and performance wise it&#039;s like moving back into the days of CGI. Forget RAM, forget concurrency, and learn to buy big boxes .  Literally if you want to see your performance increase 10x, drop the framework.  

Personally I&#039;ve taken to using a hybrid approach, use some of the framework and hand code alot. For instance lose newforms entirely it&#039;s an amazing waste of memory and cpu cycles ( still can&#039;t figure out why it exists ), don&#039;t use Models on any queries that happen alot, if you do use Models use &#039;select_all&#039;, QuerySet is the devil when it comes to speed. 

One of the most beautiful things about Django is that you can subvert Django when you need to and go straight to the underlying tools.</description>
		<content:encoded><![CDATA[<p>That is insanely bad performance, though ultimately it may be &#8220;Good&#8221; enough.  I often think people don&#8217;t realize the trade offs they make when using frameworks like Django and Rails.   Resource and performance wise it&#8217;s like moving back into the days of CGI. Forget RAM, forget concurrency, and learn to buy big boxes .  Literally if you want to see your performance increase 10x, drop the framework.  </p>
<p>Personally I&#8217;ve taken to using a hybrid approach, use some of the framework and hand code alot. For instance lose newforms entirely it&#8217;s an amazing waste of memory and cpu cycles ( still can&#8217;t figure out why it exists ), don&#8217;t use Models on any queries that happen alot, if you do use Models use &#8217;select_all&#8217;, QuerySet is the devil when it comes to speed. </p>
<p>One of the most beautiful things about Django is that you can subvert Django when you need to and go straight to the underlying tools.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: jeo</title>
		<link>http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/comment-page-1/#comment-188</link>
		<dc:creator>jeo</dc:creator>
		<pubDate>Mon, 28 Apr 2008 18:27:51 +0000</pubDate>
		<guid isPermaLink="false">http://scottbarnham.com/blog/2008/04/28/django-performance-testing-a-real-world-example/#comment-188</guid>
		<description>&quot;The wall page had a lot of queries and they increased linearly with the number of items on the wall.&quot;....eh, what? Why isn&#039;t it ONE query, no matter how many items?

Are you retrieving items then doing a separate query for the details of each one? Why aren&#039;t you just doing a join in the database?

Different types of items, stored in different tables? Do a union query with left joins. Or do a query for each type of item if you must, and sort them out in your web code.</description>
		<content:encoded><![CDATA[<p>&#8220;The wall page had a lot of queries and they increased linearly with the number of items on the wall.&#8221;&#8230;.eh, what? Why isn&#8217;t it ONE query, no matter how many items?</p>
<p>Are you retrieving items then doing a separate query for the details of each one? Why aren&#8217;t you just doing a join in the database?</p>
<p>Different types of items, stored in different tables? Do a union query with left joins. Or do a query for each type of item if you must, and sort them out in your web code.</p>
]]></content:encoded>
	</item>
</channel>
</rss>

<!-- Dynamic Page Served (once) in 0.238 seconds -->
