Saturday, May 9, 2015

Django Signals Performance Implications

Django signals are cool and very useful sometimes. They allow certain receivers get notified when certain actions occur elsewhere in the framework. For example, if we have registered a receiver function save_contribution() to listen on the post_save signal, when an article is saved, the save_contribution() will be notified and executed without explicitly calling it in the articles module. Cool, right?

However, the performance implications regarding the Django signals can be easily overlooked. An optimization I did recently reduced the number of unnecessary signals being fired from 3525 to 36 in one of our applications, which significantly speed up the sites.

Take a look at the post_init signal below:

def your_awesome_function(...):
    ...

post_init.connect(your_awesome_function)    # bad

This is bad unless you really want your_awesome_function to be called every time an object is created. What's the problem here? Well, this signal doesn't specify the Sender. As a result,  if you have a large application as we do, say, your app has 10000 objects, the function will be called 10000 times, faithfully!

post_init.connect(your_awesome_function, sender=YourModel)  # better

Now, it's better. It only gets fired when an instance of YourModel is created.

There are other cases that signals can be avoided altogether. For instance, I found a couple of unnecessary post_init signals in our applications that can be easily replaced with the functions in the base class because they really mean only for the subclasses of that base class.  

Keep in mind that signals are asynchronous, or blocking. For the performance consideration, the rule of thumb is:

1) Specify the Sender if possible.

2) Eliminate unnecessary signals.