Friday, 9 January 2009

Werkzeug and a werkzeugish Example

Someone suggested I play with Werkzeug to create the same interface so here goes..

from werkzeug import Response
from songs.utils import  expose

from collections import defaultdict

counts = defaultdict(int)

@expose('/')
def new(request):
    error = url = ''
    if request.method == 'GET':
        res = ','.join(['%s=%s' % (k, v) for k, v in counts.iteritems()])
        return Response(res,  mimetype='text/plain')
    if request.method == 'DELETE':
        counts = defaultdict(int)
        return Response('OK',  mimetype='text/plain')
    raise NotFound

@expose('/song/<id>')
def display(request, id):
    global counts
    if request.method == 'GET':
        return Response(str(counts[id]),  mimetype='text/plain')
    if request.method == 'POST':
        counts[id] += 1
        return Response(str(counts[id]), mimetype='text/plain')
    raise NotFound

This seems a little more manual but it’s concise and gets the job done.. It also performs faster than restish at ~860 req/sec. Setting up the project wasn’t quite as quick (a rest like paster template might be a nice place to start for beginners to get going quickly). As part of the comparison I wrote a ‘werzkeug’ like routine in restish

from collections import defaultdict
from restish import app, http, resource

counts = defaultdict(int)

class Root(resource.Resource):

    def __call__(self, request):
        if request.method == 'GET':
            res = ','.join(['%s=%s' % (k, v) for k, v in counts.iteritems()])
            return http.ok([], res)
        if request.method == 'DELETE':
            counts.clear()
            return http.ok([], 'OK')
        return http.method_not_allowed('GET, DELETE')

    def resource_child(self, request, segments):
        if segments[0] == 'song' and len(segments) >= 2:
            return Song(segments[1]), segments[2:]

class Song(resource.Resource):

    def __init__(self, id):
        self.id = id

    def __call__(self, request):
        if request.method == 'GET':
            return http.ok([], str(counts[self.id]))
        if request.method == 'POST':
            counts[self.id] += 1
            return http.ok([], str(counts[self.id]))
        return http.method_not_allowed('GET, DELETE')

Getting rid of the content negotiation improved the performance up to 840 req/sec. Not far away from Werzkeugs. I think one of the slowdowns in restish is the webob.Request object instantiation. Werzkeug uses a lightweight Request object and I would like to try to use something like this in restish just to see what difference it might make.

I’m pretty sure that chasing performance between 850->1200 reqs/sec for most real world applications may not be the best use of available resources. However for the example application Eric has in mind, a 30% increase in performance for a fairly simple to write application (i.e. easy of writing doesn’t get in the way too much) is probably worth it. As the application gets more complex and you need to refactor or get other eyes on the project, a simpler codebase begings to be more advantageous.

For most applications that might get 700 reqs/sec on laptop hardware (which may be 2000 reqs/sec on powerful hardware) an erlang web server may be the bees knees ;-)

POSTSCRIPT:

I used repoze.profile to work out how long we were spending in each section of the stack. For my first restish (i.e. non-optimised version), Paste is taking 18% of the time, restish is taking 28% of the time, webob is taking 34% of the time (Nearly all in Request). The rest is taken up with mapping(3%), listappending (4%), insinstance(2%), urllib/parse (5%) and string (10%) operations and then various other stuff. Looks like the best place for efficiency is in our code base and webob.

1 comments:

how to ollie said...

Excellent post and writing style. Bookmarked.

Post a Comment