views:

386

answers:

7

Hi,

I have a python (well, it's php now but we're rewriting) function that takes some parameters (A and B) and compute some results (finds best path from A to B in a graph, graph is read-only), in typical scenario one call takes 0.1s to 0.9s to complete. This function is accessed by users as a simple REST web-service (GET bestpath.php?from=A&to=B). Current implementation is quite stupid - it's a simple php script+apache+mod_php+APC, every requests needs to load all the data (over 12MB in php arrays), create all structures, compute a path and exit. I want to change it.

I want a setup with N independent workers (X per server with Y servers), each worker is a python app running in a loop (getting request -> processing -> sending reply -> getting req...), each worker can process one request at a time. I need something that will act as a frontend: get requests from users, manage queue of requests (with configurable timeout) and feed my workers with one request at a time.

how to approach this? can you propose some setup? nginx + fcgi or wsgi or something else? haproxy? as you can see i'am a newbie in python, reverse-proxy, etc. i just need a starting point about architecture (and data flow)

btw. workers are using read-only data so there is no need to maintain locking and communication between them

+1  A: 

The typical way to handle this sort of arrangement using threads in Python is to use the standard library module Queue. An example of using the Queue module for managing workers can be found here: Queue Example

Tendayi Mawushe
+2  A: 

Looks like you need the "workers" to be separate processes (at least some of them, and therefore might as well make them all separate processes rather than bunches of threads divided into several processes). The multiprocessing module in Python 2.6 and later's standard library offers good facilities to spawn a pool of processes and communicate with them via FIFO "queues"; if for some reason you're stuck with Python 2.5 or even earlier there are versions of multiprocessing on the PyPi repository that you can download and use with those older versions of Python.

The "frontend" can and should be pretty easily made to run with WSGI (with either Apache or Nginx), and it can deal with all communications to/from worker processes via multiprocessing, without the need to use HTTP, proxying, etc, for that part of the system; only the frontend would be a web app per se, the workers just receive, process and respond to units of work as requested by the frontend. This seems the soundest, simplest architecture to me.

There are other distributed processing approaches available in third party packages for Python, but multiprocessing is quite decent and has the advantage of being part of the standard library, so, absent other peculiar restrictions or constraints, multiprocessing is what I'd suggest you go for.

Alex Martelli
multiprocessing looks ok but frankly i don't need all the features (communication, synchronization, etc), my workers are independent and with this setup i need a frontend on each server, right? i was thinking about implementing basic http parsing (or something else, i dont't know what other protocol could be used between worker and revproxy) in worker and let some reverse-proxy do request handling over a pool of servers (workers are spawned at the beggining), but i don't know if this approach makes sense and where to start (which revproxy, how to implement worker<->revproxy communication etc)
winter
You don't need synchronization except for purposes of communication, but you do need communication (of tasks from frontend to workers, of results back from workers to frontend -- no, you most definitely DON'T need a frontend per server, why do you think that?!) and Queue as supplied by multiprocessing is just fine for that, freeing you from worries about protocols, proxying, and whatnot -- I think the direction you're keen to move towards may tangle you into a real mess of work, while multiprocessing's much simpler to use!
Alex Martelli
ok, but with multiprocessing i need to write 2 apps: app1 - one frontend process managing queue, app2 - many worker processes, right? then: can app1 manage app2 on different machnies? is app1 persistant? i mean - its code and data stay between requests from webserver? if no - queue is lost, if yes - it has to be async in order to handle simultaneus requests. so app1 is basically mimicking async webserver - its only purpose is to manage queue of requests. i'd like to avoid duplicating functionality that's already there it complicates everything
winter
@winter, app1 and app2 can be two functions in the same Python module, run respectively as the coordinator and as sub-processes in a pool. app1 does just manage the queues and stay around -- each of its two tasks, forwarding incoming requests to a queue and incoming responses from workers to the original requestor can trivially be made fast (and if you want to block waiting for the response, you can perfectly well choose to do that in lightweight threads that do basically no work, leaving the work to the worker processes). I don't see any duplicated functionality here.
Alex Martelli
A: 

I think you can configure modwsgi/Apache so it will have several "hot" Python interpreters in separate processes ready to go at all times and also reuse them for new accesses (and spawn a new one if they are all busy). In this case you could load all the preprocessed data as module globals and they would only get loaded once per process and get reused for each new access. In fact I'm not sure this isn't the default configuration for modwsgi/Apache.

The main problem here is that you might end up consuming a lot of "core" memory (but that may not be a problem either). I think you can also configure modwsgi for single process/multiple thread -- but in that case you may only be using one CPU because of the Python Global Interpreter Lock (the infamous GIL), I think.

Don't be afraid to ask at the modwsgi mailing list -- they are very responsive and friendly.

Aaron Watters
New processes only get spawned in Apache/mod_wsgi if using embedded mode and that that happens is a feature of Apache and not mod_wsgi. From memory described in 'http://code.google.com/p/modwsgi/wiki/ProcessesAndThreading'.
Graham Dumpleton
Oh and should also mention that using embedded mode to get that feature does have its problems where lots of data/memory is involved. See 'http://blog.dscpl.com.au/2009/03/load-spikes-and-excessive-memory-usage.html'.
Graham Dumpleton
A: 

You could use nginx load balancer to proxy to PythonPaste paster (which serves WSGI, for example Pylons), that launches each request as separate thread anyway.

iElectric
+1  A: 

There are many FastCGI modules with preforked mode and WSGI interface for python around, the most known is flup. My personal preference for such task is superfcgi with nginx. Both will launch several processes and will dispatch requests to them. 12Mb is not as much to load them separately in each process, but if you'd like to share data among workers you need threads, not processes. Note, that heavy math in python with single process and many threads won't use several CPU/cores efficiently due to GIL. Probably the best approach is to use several processes (as much as cores you have) each running several threads (default mode in superfcgi).

Denis Otkidach
thanks for links, i didn't know about superfcgi. i know about GIL and my workers are 'math' intensive so i want each of them in its own process. i don't need communication between workers. why do you think setup with processes _and_ threads is best approach? i mean, whats the point of threads when you want to compute as fast as you can?
winter
also, which layer (nginx? superfcgi? my app?) is responsible for queuing in this setup (worker = process, no threads)? example: i have 4 workers, each can process one request at a time, now i have 10 requests from users, 4 of them goes straight into workers but what about 6 that left? they should stay in FIFO queue for X sec.
winter
Listening socket is like a queue. `backlog` parameter corresponds to maximum number of requests waiting to be accepted. Each worker (process or thread) in `superfcgi` accepts only when it's ready to handle request. So OS does all queuing for you.
Denis Otkidach
A: 

The most simple solution in this case is to use the webserver to do all the heavy lifting. Why should you handle threads and/or processes when the webserver will do all that for you?

The standard arrangement in deployments of Python is:

  1. The webserver start a number of processes each running a complete python interpreter and loading all your data into memory.
  2. HTTP request comes in and gets dispatched off to some process
  3. Process does your calculation and returns the result directly to the webserver and user
  4. When you need to change your code or the graph data, you restart the webserver and go back to step 1.

This is the architecture used Django and other popular web frameworks.

knutin
ok but i want my app code and data stay between requests, i don't want interpreter to reaload at every request. is that how it works?
winter
and what happens when there is no free process (worker) to handle request? is there some king of fifo queue (with timeout)? (sorry for my newbie questions i came from php world ;)
winter
The app code and data is stored in memory for as long as the process lives, ie. forever or until you restart it. This depends on the configuration of the webserver, Apache will allow you to set a maximum number of requests per process.In general the webserver will spawn new processes when neccessary. This can be configured at length. See MinSpareServers and MaxSpareServers in the Apache docs.
knutin
A: 

Another option is a queue table in the database.
The worker processes run in a loop or off cron and poll the queue table for new jobs.

thethinman