Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Site updated at 2013-06-27 22:14:52 UTC

  • Loading branch information...
commit 98c6790cca3ddd74213151dab5703575db77bd14 1 parent 22935fb
@dcramer authored
View
2  2013/06/27/serving-python-web-applications/index.html
@@ -334,7 +334,7 @@ <h1 class="title">You Should Be Using Nginx + UWSGI</h1>
<div class="cat">
- <a class='category' href='/categories/python/'>python</a>
+ <a class='category' href='/categories/django/'>django</a>, <a class='category' href='/categories/python/'>python</a>
</div>
View
4 archives/index.html
@@ -7,7 +7,7 @@
<meta name="author" content="David Cramer">
- <meta name="description" content="Blog Archive 2013 You Should Be Using Nginx + UWSGI Jun 27 python Comments Making Django 1.5 compatible with django-bcrypt May 7 django Comments A &hellip;">
+ <meta name="description" content="Blog Archive 2013 You Should Be Using Nginx + UWSGI Jun 27 django, python Comments Making Django 1.5 compatible with django-bcrypt May 7 django &hellip;">
<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1">
@@ -87,7 +87,7 @@ <h1 class="title"><a href="/2013/06/27/serving-python-web-applications">You Shou
<div class="cat">
- <a class='category' href='/categories/python/'>python</a>
+ <a class='category' href='/categories/django/'>django</a>, <a class='category' href='/categories/python/'>python</a>
</div>
View
2  atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[David Cramer's Blog]]></title>
<link href="http://justcramer.com/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
4 blog/archives/index.html
@@ -7,7 +7,7 @@
<meta name="author" content="David Cramer">
- <meta name="description" content="2013 You Should Be Using Nginx + UWSGI Jun 27 python Comments Making Django 1.5 compatible with django-bcrypt May 7 django Comments A Weekend In &hellip;">
+ <meta name="description" content="2013 You Should Be Using Nginx + UWSGI Jun 27 django, python Comments Making Django 1.5 compatible with django-bcrypt May 7 django Comments A &hellip;">
<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1">
@@ -83,7 +83,7 @@ <h1 class="title"><a href="/2013/06/27/serving-python-web-applications">You Shou
<div class="cat">
- <a class='category' href='/categories/python/'>python</a>
+ <a class='category' href='/categories/django/'>django</a>, <a class='category' href='/categories/python/'>python</a>
</div>
View
2  categories/ci/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: ci | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/ci/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/disqus/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: disqus | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/disqus/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
361 categories/django/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: django | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/django/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
@@ -14,6 +14,213 @@
<entry>
+ <title type="html"><![CDATA[You Should Be Using Nginx + UWSGI]]></title>
+ <link href="http://justcramer.com/2013/06/27/serving-python-web-applications"/>
+ <updated>2013-06-27T13:50:00-07:00</updated>
+ <id>http://justcramer.com/2013/06/27/serving-python-web-applications</id>
+ <content type="html"><![CDATA[<p>After lots of experimentation (between <a href="http://disqus.com">disqus.com</a> and
+<a href="https://www.getsentry.com">getsentry.com</a>), I think
+we can safely say we've decided on a pretty solid best practice for squeezing
+the most peformance out of your web boxes.</p>
+
+<p><strong>I am going to convince you that nginx + uwsgi is the best, and only way you
+should be serving your Python web application, with a slight twist.</strong></p>
+
+<h1>Serving Strategies</h1>
+
+<p>There's quite a number of ways you can run a Python application. I'll
+illustrate a few for you.</p>
+
+<h2>mod_wsgi</h2>
+
+<p>Pretty straight forward. Install mod_wsgi with Apache, and either serve it
+embedded or use a wsgi daemon process.</p>
+
+<p>I'd say the daemon process is preferred, but due to the complexities of Apache
+I gave up on tuning this long ago.</p>
+
+<p>We'll skip going into configuration methodologies for mod_wsgi, as it's not
+the focus of this article.</p>
+
+<h2>gunicorn</h2>
+
+<p>When you move past mod_wsgi your solutions are basically only Python web
+servers. One of the most popular (read: trendy) methods has been gunicorn
+lately.</p>
+
+<p>We actually still recommend using gunicorn for Sentry, but that's purely out
+of inconvenience. It was pretty wasy to embed within Django, and setup was
+simple.</p>
+
+<p>It also has 10% of the configuration options as uwsgi (which might actually
+be a good thing for some people).</p>
+
+<p>Generaly running gunicorn consists of maintaining a daemon process and proxying
+it through nginx or Apache.</p>
+
+<p>Gunicorn also supports various async models, but let's be honest, evented models
+never work anywhere in Python, so unless you're sitting in the fairytale land
+where they're working, it provides you no advantages here.</p>
+
+<h2>uwsgi</h2>
+
+<p>The only alternative, in my opinion, to gunicorn is uwsgi. It's slightly more
+performant, has too many configuration options to ever understand, and also
+gains the advantage of having a protocol that can communicate with nginx.</p>
+
+<p>It's also fairly simple to setup if you can find an article on it, more on that
+later.</p>
+
+<p>I started running uwsgi with something like --processes=10 and --threads=10 to
+try and max CPU on my servers. There were two goals here:</p>
+
+<ul>
+<li>Max CPU, which required us to...</li>
+<li>Reduce memory usage, which was possible because..</li>
+<li>Sentry is threadsafe, and threads are easy.</li>
+</ul>
+
+
+<p>(For what it's worth, Disqus runs single threaded, but I'm cheap, and I wanted
+to keep Sentry as lean as possible, which means squeezing capacity out of nodes)</p>
+
+<h1>Iterating to Success</h1>
+
+<p>I was pretty proud when we got API response times down to 40ms on average. When
+I say API I'm only talking about the time it takes from it hitting the Python
+server, to the server returning it's response to the proxy.</p>
+
+<p>Unfortunately, it quickly became apparanent that there were capacity issues
+when we started getting more traffic for larger spikes. We'd hit bumpy response
+times that were no longer consistent, but we still had about 30% memory and 60%
+cpu to spare on the web nodes.</p>
+
+<p>After quite a few tweaks, what we eventually settled on was managing a larger
+amount of uwsgi processes, and letting nginx load balance them (vs letting
+uwsgi itself load balance).</p>
+
+<p>What this means, is that instead of doing uwsgi --processes=10, we ran 10
+separate uwsgi processes.</p>
+
+<p>The result was a beautiful, consistent 20ms average response time.</p>
+
+<p><img src="/images/posts/consistent-api-times.png" alt="API Times" /></p>
+
+<h1>Putting It Together</h1>
+
+<p>Because I like when people do more than talk, I wanted to leave everyone with
+some snippets of code from our Chef recipes which we used to actually set all
+of this up on the servers (with minimal effort).</p>
+
+<h2>nginx</h2>
+
+<p>The first piece of configuration is Nginx. We need to actually programatically
+add backends based on the number of uwsgi processes we're running, so things
+became a bit more complicated.</p>
+
+<p>We start by building up the list in our web recipe:</p>
+
+<p>```ruby</p>
+
+<h1>recipes/web.rb</h1>
+
+<p>hosts = (0..(node[:getsentry][:web][:processes] - 1)).to_a.map do |x|
+ port = 9000 + x
+ "127.0.0.1:#{port}"
+end</p>
+
+<p>template "#{node['nginx']['dir']}/sites-available/getsentry.com" do
+ source "nginx/getsentry.erb"
+ owner "root"
+ group "root"
+ variables(</p>
+
+<pre><code>:hosts =&gt; hosts
+</code></pre>
+
+<p> )
+ mode 0644
+ notifies :reload, "service[nginx]"
+end
+```</p>
+
+<p>Then the nginx config becomes pretty straightforward:</p>
+
+<p>```erb</p>
+
+<h1>templates/getsentry.erb</h1>
+
+<p>upstream internal {
+&lt;% @hosts.each do |host| %>
+ server &lt;%= host %>;
+&lt;% end %>
+}</p>
+
+<p>server {
+ location / {</p>
+
+<pre><code>uwsgi_pass internal;
+
+uwsgi_param Host $host;
+uwsgi_param X-Real-IP $remote_addr;
+uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
+uwsgi_param X-Forwarded-Proto $http_x_forwarded_proto;
+
+include uwsgi_params;
+</code></pre>
+
+<p> }
+}
+```</p>
+
+<p>We've now setup uwsgi to assign the number of hosts to the value of our
+web processes, started at port 9000. It's also been configured to serve
+uwsgi using it's socket protocol.</p>
+
+<h2>uwsgi</h2>
+
+<p>On the other side of things, we're using supervisor to control our uwsgi
+processes, so things are pretty straightforward here as well:</p>
+
+<p>```ruby</p>
+
+<h1>recipes/web.rb</h1>
+
+<p>command = "/srv/www/getsentry.com/env/bin/uwsgi -s 127.0.0.1:90%(process_num)02d --need-app --disable-logging --wsgi-file getsentry/wsgi.py --processes 1 --threads #{node['getsentry']['web']['threads']}"</p>
+
+<p>supervisor_service "web" do
+ directory "/srv/www/getsentry.com/current/"
+ command command
+ user "dcramer"
+ stdout_logfile "syslog"
+ stderr_logfile "syslog"
+ startsecs 10
+ stopsignal "QUIT"
+ stopasgroup true
+ killasgroup true
+ process_name '%(program_name)s %(process_num)02d'
+ numprocs node['getsentry']['web']['processes']
+end
+```</p>
+
+<h1>One Way, and Only One Way</h1>
+
+<p>Unless someone comes up with an extremely convincing argument why there should
+be another way (or a situation where this can't work), I hope to hear this
+pattern become more standard in the Python world. At the very least, I hope it
+sparks some debates on how to improve process management inside of things like
+uwsgi.</p>
+
+<p>If you take nothing else away from this post, leave with the notiion that
+<strong>uwsgi is the only choice for serving threaded (or non) python web
+applications</strong>.</p>
+
+<p>(I hastily wrote this post to illustrate some findings today, so pardon the
+briefness and likely numerous typos)</p>
+]]></content>
+ </entry>
+
+ <entry>
<title type="html"><![CDATA[Making Django 1.5 compatible with django-bcrypt]]></title>
<link href="http://justcramer.com/2013/05/07/making-django-1-dot-5-compatible-with-django-bcrypt"/>
<updated>2013-05-07T08:43:00-07:00</updated>
@@ -407,156 +614,4 @@ tell you that we rent servers successfully, and by we, I mean <a href="http://di
]]></content>
</entry>
- <entry>
- <title type="html"><![CDATA[The Cloud is Not For You]]></title>
- <link href="http://justcramer.com/2012/06/02/the-cloud-is-not-for-you"/>
- <updated>2012-06-02T13:57:00-07:00</updated>
- <id>http://justcramer.com/2012/06/02/the-cloud-is-not-for-you</id>
- <content type="html"><![CDATA[<p><strong>Update:</strong> Did I hurt your feelings with this post? Read
-<a href="http://justcramer.com/2012/06/03/scaling-your-clouds/">Scaling your Clouds</a> so you can rage even more.</p>
-
-
-
-
-<p>Well, maybe not specifically <em>you</em>, but the mass that screams it will solve their problems.</p>
-
-
-
-
-<p>It's been a fun year so far. There's been exciting things happening both for me personally, as well as at DISQUS. One of
-those things has been launching a new side project, <a href="https://www.getsentry.com">getsentry.com</a>. This is about
-it's 4th month running publicly, and it's been doing very well. I wanted to talk a little bit about where it started, and
-how quickly it's shifted in the stance of where it's going.</p>
-
-
-
-
-<p>Around Christmas of 2011, and after a lot of prodding by <a href="http://www.craigkerstiens.com/">Craig Kerstiens</a> (of Heroku)
-I had finally given in to the pressure of creating a hosted version of Sentry to launch as a Heroku addon. I already knew
-Sentry was awesome, as did many others, and this just meant getting something I put a lot of effort into out in front of
-so many others. It was very little work to get things up and running on Heroku, and just as easy to setup the addon
-endpoints. We started a private beta shortly thereafter, and immediately picked up a bunch of the Django/Python crowd.</p>
-
-
-
-
-<p>From there it slowly, but steadily grew in both customers and data. In fact, for the first couple of months we were able
-to survive on just a few dynos and the first tier of dedicated postgres (which was the $200 package at the time). We've
-also expanded to cover nearly all popular languages, including PHP, Ruby, Java, and even JavaScript.</p>
-
-
-
-
-<p>A bit further in the background of how I structured the Sentry service:</p>
-
-
-
-
-<ul>
- <li>Two separate apps (www and app)</li>
- <li>SSL everywhere (two certs, two addons, $40/month plus SSL cert costs)</li>
- <li>A minimum of two dynos each ($72/month~)</li>
- <li>Tier-1 dedicated DB (Ronin, $200/month)</li>
-</ul>
-
-
-
-
-<p>Now, before I continue, let me say that I thoroughly enjoyed using Heroku. It's a great service, I'm friends with a lot
-of people there. That said, I want to explain why you shouldn't use Heroku, or the cloud. Let me also clarify that I'm not
-talking about the limitations of the idea of the cloud, but more specifically the limitations I've seen from providers,
-and specifically my experience with Heroku.</p>
-
-
-
-
-<p>Right from the get-go we had a system that had pretty good HA and redundancy, especially due to how Heroku's Postgres
-solution works. Unfortunately, we quickly saw the limitations of what both the Postgres and the dynos could handle.</p>
-
-
-
-
-<p>Our first attempt to address this was to add worker nodes (ala Celery) to handle concurrency better. This turned into one
-or two additional dynos dedicated to processing jobs, as well as an additional Redis addon. Unfortunately the Redis addon
-is completely overpriced, we quickly shifted to pulling up a VM in Linode's eastcoast datacenter instead. This bought us
-a little bit of time, but really I'd say we were only given an additional 10% capacity by what should have been a large
-optimization.</p>
-
-
-
-
-<p>Another week or two went by, and it was suggested that we get off the Ronin database, and upgrade to the Fugu package (
-which bumped up the database cost to $400/month). This did quite a bit. In fact, this let us handle most things without
-too much of a concern. A little while down the road, we had a customer sign up who was actually send realistic amounts of
-data. More specifically, <strong>not even close to the amount of data Disqus' Sentry server handles</strong>, but about 10x
-more than the rest of our customers combined had been sending.</p>
-
-
-
-
-<p>Then shit started to hit the fan.</p>
-
-
-
-
-<p>In no specific order, we started finding numerous problems with various systems:</p>
-
-
-
-
-<ul>
- <li>Redis takes too much memory to reliably queue Sentry jobs.</li>
- <li>Dynos are either memory or CPU bound, but we have no idea how or why.</li>
- <li>The Postgres server can't handle any reasonable level of concurrency.</li>
- <li>We randomly have to spin up 20 dynos to get anywhere in the queue backlog.</li>
-</ul>
-
-
-
-
-<p>Given all of that, I made the decision that I was going to go back to using real hardware and managing it myself. I'm
-no stranger to operations work, though it's never been my day job. I did however want to do this right, and with the advice
-of my coworker, friend, and roommate, <a href="https://twitter.com/#!/sugarc0de">Mike Clarke</a> I decided I'd set these
-up properly, with Chef.</p>
-
-
-
-
-<p>About three days into it, and I had learned how to use Chef (I don't write Ruby), brought up two full pluggable
-configurations for a db node and a web node, written a deployment script in Fabric, migrated to the new hardware and
-destroyed my Heroku and Linode instances. Three days, that's all it took to replace the cloud.</p>
-
-
-
-
-<p>Now you might argue that the cloud let's you scale up easily. <strong>YOU ARE WRONG, IT DOES NOT.</strong> The cloud
-gives you the convenience, or more importantly, the illusion of convenience, that you can bring up nodes to add to your
-network without giving it much thought. You can do that. You don't ever realistically need to do that.</p>
-
-
-
-
-<p>Almost any company worth a damn can bring online a server within 24 hours, even budget companies. When have you actually
-needed turnaround time faster than that? If you did, maybe you should read up on capacity planning.</p>
-
-
-
-
-<p>The hosted Sentry now runs on two budget servers, one of which runs Postgres, pgbouncer, and Redis, the other handles
-Nginx, Celery, memcached, and the Python webserver. The cost for these two machines? About $300/month. When I destroyed
-Heroku, my bill was looking to be around $600-700 between Heroku and Linode. Given the numbers we run at Disqus, the
-physical hardware should be able to handle no less than 2000% the capacity I was struggling to handle on the cloud.</p>
-
-
-
-
-<p>I'm not saying you can't make use of the cloud. For example, Disqus uses Amazon for running large amounts of map/reduce
-work. You know, <strong>elastic computing</strong>, the kind of computing that is inconsistent, unplanned, or generally
-infrequent. I'm also not saying you shouldn't use Heroku. You should see if it works for you. However, if you ever come up
-to me and argue that the cloud is going to fix any problem, I'll make the assumption that you're one of those annoying
-kids that runs around screaming MongoDB and Node.js are the answer to all of the worlds problems.</p>
-
-]]></content>
- </entry>
-
</feed>
View
24 categories/django/index.html
@@ -76,6 +76,30 @@ <h1 class="archive-title">2013</h1>
<article class="archive">
+<h1 class="title"><a href="/2013/06/27/serving-python-web-applications">You Should Be Using Nginx + UWSGI</a></h1>
+<div class="meta">
+ <span class="date">Jun 27</span>
+ <span class="tags">
+
+<div class="cat">
+
+ <a class='category' href='/categories/django/'>django</a>, <a class='category' href='/categories/python/'>python</a>
+
+</div>
+
+</span>
+
+ <span class="comments"><a href="/2013/06/27/serving-python-web-applications#disqus_thread">Comments</a></span>
+
+</div>
+ </article>
+</section>
+
+<section class="archive">
+
+
+ <article class="archive">
+
<h1 class="title"><a href="/2013/05/07/making-django-1-dot-5-compatible-with-django-bcrypt">Making Django 1.5 compatible with django-bcrypt</a></h1>
<div class="meta">
<span class="date">May 7</span>
View
2  categories/git/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: git | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/git/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/heroku/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: heroku | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/heroku/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/howto/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: howto | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/howto/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/ops/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: ops | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/ops/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/osx/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: osx | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/osx/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/postgresql/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: postgresql | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/postgresql/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/python/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: python | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/python/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/python/index.html
@@ -83,7 +83,7 @@ <h1 class="title"><a href="/2013/06/27/serving-python-web-applications">You Shou
<div class="cat">
- <a class='category' href='/categories/python/'>python</a>
+ <a class='category' href='/categories/django/'>django</a>, <a class='category' href='/categories/python/'>python</a>
</div>
View
2  categories/sentry/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: sentry | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/sentry/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  categories/solr/atom.xml
@@ -4,7 +4,7 @@
<title><![CDATA[Category: solr | David Cramer's Blog]]></title>
<link href="http://justcramer.com/categories/solr/atom.xml" rel="self"/>
<link href="http://justcramer.com/"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<author>
<name><![CDATA[David Cramer]]></name>
View
2  disqus.xml
@@ -2,7 +2,7 @@
<feed xmlns="http://www.w3.org/2005/Atom">
<title>JustCramer</title>
<link href="http://justcramer.com/atom.xml" rel="self"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
View
38 django.xml
@@ -2,12 +2,32 @@
<feed xmlns="http://www.w3.org/2005/Atom">
<title>JustCramer</title>
<link href="http://justcramer.com/atom.xml" rel="self"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<entry>
+ <title>You Should Be Using Nginx + UWSGI</title>
+ <link href="http://justcramer.com/2013/06/27/serving-python-web-applications"/>
+ <updated>2013-06-27T13:50:00-07:00</updated>
+ <id>http://justcramer.com/2013/06/27/serving-python-web-applications</id>
+ <content type="html">After lots of experimentation (between disqus.com and
+getsentry.com), I think
+we can safely say we&amp;#8217;ve decided on a pretty solid best practice for squeezing
+the most peformance out of your web boxes.
+
+I am going to convince you that nginx + uwsgi is the best, and only way you
+should be serving &amp;hellip;</content>
+ <author>
+ <name>David Cramer</name>
+ <url>http://justcramer.com/</url>
+ </author>
+ </entry>
+
+
+
+ <entry>
<title>Making Django 1.5 compatible with django-bcrypt</title>
<link href="http://justcramer.com/2013/05/07/making-django-1-dot-5-compatible-with-django-bcrypt"/>
<updated>2013-05-07T08:43:00-07:00</updated>
@@ -157,20 +177,4 @@ Python world, and because I have an &amp;hellip;</content>
</entry>
-
- <entry>
- <title>Scaling Schema Changes</title>
- <link href="http://justcramer.com/2011/11/10/scaling-schema-changes"/>
- <updated>2011-11-10T16:06:00-08:00</updated>
- <id>http://justcramer.com/2011/11/10/scaling-schema-changes</id>
- <content type="html">I frequently get asked how Disqus deals with schema changes. It&amp;#8217;s a fair question, since we operate a fairly large amount of servers, but I also tend to think the answer is somewhat obvious. So let&amp;#8217;s start with the problem of schema changes at scale (in PostgreSQL).
-
-Generally you have &amp;hellip;</content>
- <author>
- <name>David Cramer</name>
- <url>http://justcramer.com/</url>
- </author>
- </entry>
-
-
</feed>
View
2  feeds/disqus.xml
@@ -2,7 +2,7 @@
<feed xmlns="http://www.w3.org/2005/Atom">
<title>JustCramer</title>
<link href="http://justcramer.com/atom.xml" rel="self"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
View
38 feeds/django.xml
@@ -2,12 +2,32 @@
<feed xmlns="http://www.w3.org/2005/Atom">
<title>JustCramer</title>
<link href="http://justcramer.com/atom.xml" rel="self"/>
- <updated>2013-06-27T15:13:49-07:00</updated>
+ <updated>2013-06-27T15:14:29-07:00</updated>
<id>http://justcramer.com/</id>
<entry>
+ <title>You Should Be Using Nginx + UWSGI</title>
+ <link href="http://justcramer.com/2013/06/27/serving-python-web-applications"/>
+ <updated>2013-06-27T13:50:00-07:00</updated>
+ <id>http://justcramer.com/2013/06/27/serving-python-web-applications</id>
+ <content type="html">After lots of experimentation (between disqus.com and
+getsentry.com), I think
+we can safely say we&amp;#8217;ve decided on a pretty solid best practice for squeezing
+the most peformance out of your web boxes.
+
+I am going to convince you that nginx + uwsgi is the best, and only way you
+should be serving &amp;hellip;</content>
+ <author>
+ <name>David Cramer</name>
+ <url>http://justcramer.com/</url>
+ </author>
+ </entry>
+
+
+
+ <entry>
<title>Making Django 1.5 compatible with django-bcrypt</title>
<link href="http://justcramer.com/2013/05/07/making-django-1-dot-5-compatible-with-django-bcrypt"/>
<updated>2013-05-07T08:43:00-07:00</updated>
@@ -157,20 +177,4 @@ Python world, and because I have an &amp;hellip;</content>
</entry>
-
- <entry>
- <title>Scaling Schema Changes</title>
- <link href="http://justcramer.com/2011/11/10/scaling-schema-changes"/>
- <updated>2011-11-10T16:06:00-08:00</updated>
- <id>http://justcramer.com/2011/11/10/scaling-schema-changes</id>
- <content type="html">I frequently get asked how Disqus deals with schema changes. It&amp;#8217;s a fair question, since we operate a fairly large amount of servers, but I also tend to think the answer is somewhat obvious. So let&amp;#8217;s start with the problem of schema changes at scale (in PostgreSQL).
-
-Generally you have &amp;hellip;</content>
- <author>
- <name>David Cramer</name>
- <url>http://justcramer.com/</url>
- </author>
- </entry>
-
-
</feed>
View
2  index.html
@@ -341,7 +341,7 @@ <h1 class="title"><a href="/2013/06/27/serving-python-web-applications">You Shou
<div class="cat">
- <a class='category' href='/categories/python/'>python</a>
+ <a class='category' href='/categories/django/'>django</a>, <a class='category' href='/categories/python/'>python</a>
</div>
Please sign in to comment.
Something went wrong with that request. Please try again.