

Using Nginx and UWSGI - zeeg
http://justcramer.com/2013/06/27/serving-python-web-applications/

======
hosay123
For a little perspective, the remaining 20ms still leaves enough time for oh,
55 random SSD IOs _and_ time spare to zlib a megabyte of text. Or about 4.8
million instructions of a modern 2.4ghz CPU. Just what does this app do
exactly?

Aah.. Django.

Seriously, 20ms _isn 't good even for Python_. That's not something to be
proud of, that's reason to rip up whatever overelaborate mess you've
constructed to figure out just how you're blowing so much time.

As a reminder, a 200 byte HTTP request can be parsed in around 600
instructions or less (about 250 nanoseconds at 2.4ghz).

~~~
zeeg
In many cases, it uncompresses 1mb of text.

Bet you didn't see that one coming

~~~
zeeg
Also it does nothing with the disk, but does quite a bit over the network
(queueing data, talking to caches).

I'm content with 20ms.

Hell, I'm content with 100ms, as long as its consistent and you can argue the
value.

------
ra
The real strength of uwsgi is it's emperor mode [1].

On my servers I have one upstart job whose role is simply to start the emperor
daemon and make sure /var/run/uwsgi permissions are correct for writing sock
files.

Each django project deployed on the server has a uwsgi.ini file in it's etc
folder. This ini file holds the config for that app only. It defines where the
socket file is located, where the virtualenv is located and where the
project.wsgi file is located.

It's simple because you can host multiple websites on a single server, but you
uwsgi configuration is separated into 'system' and 'project', so your
'project' specific config can live in git and your 'system' config never has
to change.

Nginx is setup in a very similar way.

[1] [http://uwsgi-docs.readthedocs.org/en/latest/Emperor.html](http://uwsgi-
docs.readthedocs.org/en/latest/Emperor.html)

~~~
tghw
Agreed. I did a writeup of my setup about a year ago:

[http://tghw.com/blog/multiple-django-and-flask-sites-with-
ng...](http://tghw.com/blog/multiple-django-and-flask-sites-with-nginx-and-
uwsgi-emperor)

------
krenoten
This is pretty light on the details. I also use Nginx + uWSGI in front of an
API that shakes up a lot of network IO behind it, and it has had a few
headaches. On FreeBSD it ignores the die-on-term option, making it a pain to
use in conjunction with service managers that rely on SIGTERM like
daemontools. It's good that in your example you include the need-app option,
as it will happily start up without being able to load anything otherwise. I'm
curious what the intended default use case is, of mostly just wanting the
server up - oh, and if you manage to load my app that would be ok I guess but
don't sweat it! :P I disagree with a few choices in how it load balances, and
tuning the backlog has proven futile with my setup. But yeah, it's lightyears
ahead of mod_wsgi.

If you're a control freak who bases engineering decisions primarily on
flexibility (and are bound to python/WSGI) why not use Mongrel 2 with a simple
python backend instead? You can escape having to shoehorn your code into a
gevent/gunicorn/uwsgi/whatever container if it causes headaches to do so.
Here's a teaching example for how to play with mongrel 2 from python (feel
free to change it into something that would work for you)
[https://github.com/tizr/belasitsa/blob/master/belasitsa.py](https://github.com/tizr/belasitsa/blob/master/belasitsa.py)

~~~
zeeg
What kind of headaches?

I'm not aware of anyone actually using Mongrel 2 in a large scale Python
application, but I'd be curious to know how well it works out.

~~~
krenoten
A few configuration issues while we learned which ones performed as described
on FreeBSD. It loads python modules BEFORE forking workers, so if you use
multiprocessing or anything to create procs it will do it once before the
fork, and all post-fork children are left sharing the same pool unless you
instantiate it during the first request or a similar approach. These are just
things to be aware of, and are not necessarily black marks against its
usefulness assuming you are willing to accept a certain cost of learning the
features (there are a lot). I'm still using it, but I'm not really convinced
that I'm better off than if we had picked gunicorn or similar initially and
skipped some headaches.

------
FooBarWidget
Both gunicorn and uwsgi are good stuff. Though a new contender is Phusion
Passenger
([https://www.phusionpassenger.com/](https://www.phusionpassenger.com/)),
originally designed for Ruby but recently gained official support for Python.
With the next release (4.0.6), Python support will be raised from beta status
to production-ready status. Like uwsgi, it performs the bulk of the process
management for you. It goes a step further than uwsgi by integrating directly
into Nginx, so that the web server is the only thing you have to deal with.

------
mixmastamyk
A couple of years ago there was an article comparing the various wsgi servers.
The one that seemed most performant/low memory at the time was gevent, so I
chose it for the application I was writing at the time to run on an ec2 micro
behind nginx. It was very easy to use too, just wrote a 10 line python script
and that was it.

I've never seen any articles about it since, so I wonder if others know
something I don't. Can anyone compare gevent's webserver vs. uwsgi, gunicorn,
etc behind nginx?

~~~
zeeg
gevent is a very simple webserver that wont handle many things that the "real"
servers do.

One of the main things I'm talking about in this post is load balancing, which
gevent (and uwsgi, and gunicorn, etc) don't implement very well. Nginx does a
pretty good job at this, in addition to many other things (like buffering).

You can definitely get a lot of performance out of a coroutine, async, etc
framework, but at the end of the day it's not going to be the entry point to a
production application.

~~~
zhemao
I think OP was asking what the performance of gevent server running behind
nginx was compared to uwsgi or gunicorn running behind nginx.

~~~
mixmastamyk
Yes I do run it behind nginx. While performance is important, I wonder about
the other aspects as well.

------
codysoyland
Stopped reading at "evented models never work anywhere in Python."

Gunicorn+gevent has been painless for me.

------
stevenwei
I've been running Nginx + uWSGI for a long time but recently switched over to
Nginx + Gunicorn for Gevent support, and so far it seems to be working well.

Gevent's monkey patching approach allowed me to convert a traditional blocking
web app (built on Flask/SQLAlchemy) into an evented model without having to
rewrite the entire app around an event-driven approach (ala Twisted or
Tornado). All you have to do to get Gevent working with Gunicorn is to pass it
a command line argument to use the Gevent worker instead of the default
synchronous worker.

Gunicorn + Gevent also makes it quite easy to integrate WebSockets into your
existing web app, which is something uWSGI doesn't handle very well yet.

All this is to say that though uWSGI is a very solid piece of software,
Gunicorn is definitely a reasonable alternative to uWSGI depending on your use
case.

~~~
unshift
gunicorn + meinheld is even better than gevent in my experience. meinheld is
written in C and offers a monkey patch option, and is a little bit faster than
gevent. i'm using it with flask + sqlalchemy + redis with great success.

------
m0th87
I don't get why this is getting so many points. It consists of a single graph
and a bunch of hand-waving. Python doesn't have to be like the ruby community;
it doesn't have to have a one-size-fits-all solution.

We're happily using tornado in production. Between the heroku router and
tornado's built-in support for multi-processing _and_ asynchronous processing,
I don't have to worry about nginx + uwsgi + whatever framework. I just run
tornado. And we're immune from the heroku random routing debacle [1],
something that neither rails nor uwsgi can say.

1: [http://news.rapgenius.com/James-somers-herokus-ugly-
secret-l...](http://news.rapgenius.com/James-somers-herokus-ugly-secret-
lyrics)

~~~
zeeg
UWSGI has absolutely nothing to do with the link you posted, nor does tornado.

~~~
m0th87
Tornado certainly does. We convert our most I/O intensive bits to asynchronous
code, and the server is no longer blocked. Random routing doesn't affect us
because there's no long pauses in, e.g. making a third-party API request.

Maybe I'm misunderstanding uwsgi though?

~~~
zeeg
I'm not using uwsgi as a router, and the speed of the requests isnt the issue.

If for example, you randomly routed to a bunch of tornado servers that were
overloaded/slow serving requests, you'd hit the same issue as that post.

Async/coroutines dont solve the problem, better routing does.

In this case, we use HAProxy, and you ideally would say "I know node X can
handle 100 concurrent requests), and within HAProxy you could weight the
node/set the maximum concurrent requests to said node, and it would distribute
based on that.

~~~
m0th87
The point is that the probability of the servers getting overloaded is greatly
diminished, because they're not blocked for extended periods of time. An
external API request can take several seconds. In that time, a framework that
isn't async-capable is going to be blocked and unable to work on more requests
in the dyno queue. Tornado (and node, etc.) can continue happily along.

On top of that, tornado has multi-processing support built-in, which adds
intelligent routing at the dyno-level.

HAProxy is another solution, although it can't be setup on heroku so that's
irrelevant. Also, it doesn't change the fact that your workers are blocked
while executing long-running API requests. uwsgi won't save you either.

The linked doc literally says as much, toward the bottom: "So the only
solution is for Heroku to return to routing requests intelligently. They claim
that this is hard for them to scale, and that it complicates things for more
“modern” concurrent apps like those built with Node.js and Tornado. But Rails
is and always has been Heroku’s bread and butter, and Rails isn’t multi-
threaded.

In fact a routing layer designed for non-blocking, evented, realtime app
servers like Node and its ilk — a routing layer that assumes every dyno in a
pool is as capable of serving a request as any other — is about as bad as it
gets for Rails, where almost the opposite is true: the available dynos are
perfectly snappy and the others, until they become available, are useless. The
unfortunate conclusion being that Heroku is not appropriate for any Rails app
that’s more than a toy."

------
jaytaylor
Uhm...am I misunderstanding something? It sounds to me like the author is
saying we should be spending time tuning UWSGI's plethora of configuration
options rather than using gunicorn. I much prefer components which "just work"
for a healthy range of typical use cases.

Don't get me wrong, I think it's cool he made it work.. but seriously?

~~~
zeeg
There was absolutely no tuning other than adjusting buffer sizes (which only
matters because it accepts large POST packets).

The options that a standard application will use are almost identical between
the two. UWSGI just provides numerous lower-level options that I (and it
sounds like you) probably don't care about.

~~~
jaytaylor
Thank you for the clarification.

------
hendzen
Average response time can be misleading. I'd like to see what the 95%
percentile response time is.

~~~
zeeg
I actually lied here, this is the 90th percentile, and average is a couple ms
higher.

That'll teach me to burn through a post quickly

------
mixedbit
uwsgi is great, but only for users that want to do a PhD in performance
tuning:

'There is no magic rule for setting the number of processes or threads. It is
application and system dependent. Do not think using simple math like
2*cpucores will be enough. You need to experiment with various setup an
constantly monitor your app. uwsgitop could be a great tool to find the best
value.' (from uwsgi doc)

------
seanp2k2
Why is "Max CPU" a stated goal?

~~~
zeeg
To maximum the use of a node. If you cant use all of the CPU because you don't
have enough memory, than the node (at peak times) is wasteful.

------
drivebyacct2
Should I be using Nginx + UWSGI with Go apps? I know someone wrote a seemingly
comprehensive uwsgi package for Go, but IIRC it kinda got shredded a bit in
the mailing list.

edit: Hm, maybe there's a different thread or I was discouraged from using it
in IRC or I'm inventing memories because this ml post is fairly lackluster
either way: [https://groups.google.com/d/msg/golang-
nuts/2vHlfkpS-m0/MZ-X...](https://groups.google.com/d/msg/golang-
nuts/2vHlfkpS-m0/MZ-Xgdy45gcJ)

~~~
tghw
To my knowledge, WSGI is pretty Python-specific. Does Go even support WSGI? I
tried looking but Go is a terrible term to try searching for.

~~~
huxley
Using Golang instead of Go works for a good subset of searches.

Here's the docs on UWSGI's Go support:

[https://github.com/unbit/uwsgi-
docs/blob/master/Go.rst](https://github.com/unbit/uwsgi-
docs/blob/master/Go.rst)

------
MostAwesomeDude
So, as far as I can tell, you have failed to demonstrate that Twisted Web with
PyPy is not a viable alternative, which is disappointing.

Also, uWSGI's core team doesn't have any faith in their own code; they use
Apache instead of dogfooding. (And they're moving all hosting and development
to sites like Github.) Twisted, at least, is confident enough in the quality
of their code to self-host.

~~~
zeeg
PyPy uses too much memory and isnt that much faster for many things.

(Sorry PyPy guys!)

I dont have numbers/charts to pull up for this right now, but I was testing it
out to see if I could push more cpu on a server, and the memory cost was
insane for the minimal test case.

Twisted also is not a web server, it's a framework. Same with Tornado, and
honestly, the same with gevent/etc.

~~~
sillysaurus
How much memory usage is "insane"? Are you sure the memory cost grows with the
size of the application? It sounds like you tested a minimal test case, saw
high memory usage, then didn't bother to test a large application. Maybe the
memory usage is constant.

~~~
zeeg
I saw 5x memory usage, which was already something I was trying to optimize.

There's a ticket I opened on the PyPy tracker, but this is sort of expected if
I understand JITs correctly.

I didn't exhaustively benchmark it, but the initial results were too far
outside the bounds of my limitations.

~~~
sillysaurus
5x memory usage of what? You're not giving actual numbers, nor the benchmark
you used.

~~~
coldtea
> _5x memory usage of what?_

Of what he was seeing without PyPy.

He doesn't want advice on how to lower that. He wants to get the same memory
usage or better out of the box.

~~~
zeeg
No I wanted reasonable tradeoffs.

e.g. 20% faster execution for 100% more memory

In my case it was 15% faster execution (give or take), with a 400% memory
increase.

This was on a simple view which did nothing, so it's hard to tell how it'd
handle more complex views in the app.

Either way this is a huge detour from what this actual topic is about, whether
you use PyPy or CPython

