Transcript: Performance
Hello and welcome to another episode of Django Chat, a weekly podcast on the Django web framework.
I'm Will Vincent, joined as always by Carlton Gibson.
Hi, Carlton.
Hello, Will.
And this week we're going to talk about performance.
So performance matters because it is the probably most important part of the user experience.
Google punishes slow sites with SEO these days, and even something like Amazon with e-commerce has done studies showing that just a 100 millisecond slowdown can cost a percentage of sales.
And what's 100 milliseconds?
That's like a blink of an eye.
What's the actually as we get into it?
What's the default these days for how was it 300 milliseconds a user can't tell a difference?
But then after that, every 100 milliseconds, I think.
Oh, well, there was so I don't know, but there was something about iPhone um back in the day when I was playing with those sorts of things.
The um there was something about the responsiveness.
There was a 300 millisecond click clicking in a wet in a a web view, and it used to drive users mad.
So people would be building web apps, no native apps, but using web views.
And there'd be this this this noticeable delay.
Yeah, yeah, yeah.
Versus a native button which just went off straight away.
And I think the delay was 300 milliseconds, and they that there was some can we get rid of it?
No, we can't get rid of it because of this.
Uh but that
was like a glitch in the system, yeah, and it just gets super frustrating.
So I think 300 milliseconds definitely is noticeable.
Yes, yes.
But I don't know why is it.
So we're gonna go through it's a whole set of tools and approaches.
Yeah, yeah, go ahead.
Well, no, web web framework, uh no, web performance metric was um they talked about less than one second screen um to glass.
Like if you could if you if your full load time to to interactive on the glass was less than a second, then users considered that fast.
And that's like the the gold standard.
That's having your your your HTML delivered, your JavaScript on on there, your your um CSS in place.
So at least your first layout done, even if you're pulling in images and things like that, but also your page clickable and the two glasses that that it's responsive, that it's it's rendered and responsive in under a second, and that's considered like you know, that's quick.
That's and you're talking about on mobile here.
Right.
I mean on a website I say it's less, and I think over time people have come to expect better.
Uh but then of course no, but that I think that's pretty good.
You bang that into even sit it's at your desktop, even with a decent connection, you put that into um your your desktop browser and you type any any site that's loading any kind of JavaScript, and like half of them will be slower than a second.
So anyway, that that that that that one second to glass idea is is kind of like a benchmark.
Um and if you think about how long it takes to load JavaScript and for it to um well, all the assets to arrive, you've got network latency, and then you've got the loading time, then you've got the rendering time.
It doesn't give your Django web application very much time to respond if you're gonna meet that.
Right, exactly.
And uh last point before we get into all this, I do want to mention uh Donald Knut.
Is that how you say his name?
Um Knut.
Knuth.
Knut.
Yeah, says says Wikipedia.
I always thought it was neuth, I was going new for years, and then I looked it up.
It's Knuth.
So Knuth is a he's a Stanford, you know, he looks at email, I think like once every six months and thinks all these deep thoughts, and he has these incredible series on computer science.
Anyways, here's his quote on performance before we get into it, which is quote, the real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times.
Premature optimization is the root of all evil, or at least most of it in programming.
So you've probably heard the second half of that, but I think it's in context, it makes a lot of sense around uh as we get into all these things.
Basically think about what you're doing, don't just blindly whack every performance efficiency mole that pops up, because those will be infinite.
But also, like what's it turns out as a sort of matter of fact, just from you know how programs behave in the wild, that most of your performance issues will come from a very small number of places and you won't be able to predict where they are in advance.
So, what you need to do is build it, just build it simply and as sanely as you can.
Don't make optimizations, don't you know, just don't spend time optimizing it at all, and then profile it and see where the the three performance bottlenecks which are taking 70% of the time are, optimize those, and with for a fraction of the effort of micro-optimizing everything as you went along, you've got a more performant web application.
Yes.
But it is tempting to try to do it locally.
So yeah, well it's it's interesting, isn't it?
Oh, okay.
You know, can I get can I if I use exists versus count, do I get a four millisecond?
Yeah, okay, well let's get into that.
So how do you baby step up it?
So the very first thing I would say is you have to have Django debug toolbar.
Just to that's a third-party package.
Uh it let gives you configurable panes, uh configurable panels so you can see the request response cycle of a page.
Basically, it shows you how many queries are there and how long it takes to load locally.
So this isn't a proxy for production, but it gives you a quick look at it.
And the two big ones, again, do this in production.
The two big things you're gonna want to look at for queries to is select related and prefetch related.
Do you want to take a stab at those, Carlton?
Okay, so so if you so there are, as well as Django debug toolbar, which you'd use locally, there are things called um application APM.
What is the P stand for?
Application something monitoring.
I can never process it.
Yeah, process.
But let's pretend it's process.
It might be something into it.
It could be performance.
Um but anyway, APM.
So um there used to be one called Otbeat, which got brought up by Elastic, and then there are other ones out there.
Um what's the New Relic was
New Relic Rollbar?
Is that one of those?
I always think Sentry should have one, but I don't think they do.
I think they just have to verify.
But I think rollbar is um datadog, I think.
Right, okay.
But anyway, there's there's loads of these, and they just wrap something around your application which monitors how long execution times take.
And what you're gonna find if you've got any old normal application is that your biggest hit is database um is the time to fetch data from the database that's most of your response time um from your Django application.
So what select related does is it when there's a foreign key, you can say, hey, can we just join those two together with a SQL join?
And can we get them all in one database hit rather than um two or more?
Um, you know, if it's
one larger
each for a related object.
Um and then there's the the other, so the other option is prefecture related, which is for many to many or many to one uh relationships, where you want to fetch um, I don't know, all the authors and all the books or I don't know together.
Can a book have yeah, a book could have many authors, so that's fine.
Um and what that will do is it'll fetch the authors and then it will fetch all the books that are related to the authors, and it will do that in in a couple of database queries rather than it can't do it in in one because it can't do the join, but it will do it in two database queries rather than you know perhaps potentially hundreds.
And I think uh the history of Django, so select related was always there, and then I believe pre-fetch related was added later.
Yeah, I'd have to go and look to be honest.
But yeah, that's
these are the these are the two hammers that you're gonna want to use as a as a first step, generally speaking, when you see a page that's loading slowly.
And Django's test suite has a really cool cool tool called assert num queries, which if you write a unit test fetching the data you want, I haven't used that.
You can you can assert that only one query was made when you use select related to fetch your data.
And so you can kind of
know something, Carlton, sometimes.
Look at that.
Well, I just I found it in the Django test suite.
I'm like, what's that?
That's quite quite exciting.
But yeah, so you can so you can write a unit test fetching your, you know, so say you've got a convenience method which wraps all all the data you need for your view and returns it nicely.
So you you know, so you keep that logic out of the main line of the view.
You can test that method with use with a certain number of queries and say, look, I'm expecting this to make two queries because I'm using prefetch related.
I want one for the authors and one for the books, and I don't want any more queries.
Um so that when you you know iterate through your list in your test, it says, yeah, I did fetch all of the objects here in two queries rather than one for them for the author and then one for each of the books as I traverse the relationship.
Right.
Yeah, I like that.
Okay, I'm gonna have to use that.
Uh so
what else?
So yeah, so reduce the number of queries, right?
That's the first thing.
And then make sure your queries are efficient.
So that's the second thing I'd say is indexes.
So look at the qu look at the queries you're making in your views, and then make sure that those uh you can use explain, um, the database explain.
E query sets now from 2.2 have an explain method, which is kind of nice.
It saves you having to extract the query from the query set using query.query, and then putting that into your shell to explain it, you can just call explain and it will give you, it'll send that off to the database and ask it to explain, it says what it's doing.
And you have to you have to do that a few times and read them, but it will say, look, and now I'm doing a row scan.
And what a row scan means is I'm going through every single row of the database table to see what the matches are.
And what you don't want that, that's where you want an index, because you want it to just go and look up for in the index and get the matching values from the index, which is a much quicker operation, right?
So reduce the number of queries and make sure you're using index correctly.
That's my big one and two.
And then you were just about to say caching.
Yes.
Which we have a whole episode on this, and yeah,
we talked about that briefly before.
Yeah.
Um so cache all the things.
Uh well, yeah, but well, you know, if it took a long time to get out of the database and you're getting it all the time, cache it.
I I I remember I worked on a um site which was uh an API which was serving social media data.
I don't know, it was a competitor to some one of these um I can't even remember.
Say so long ago.
But anyway, it was it was social media data mining nonsense.
And they had clients that were um making lots and lots and lots of API requests all the time.
And every every request they had to um check the API key.
So you don't want to go fetching all the API keys from the database, every single request just to check whether the API matched.
So we would fetch it once an hour or whatever, and we would then check against the cache whether the API key was correct rather than against the database, because well, that's quicker.
Right.
But the key thing is you're doing this on real live production data.
Because again, I'll I'll say this again to folks don't waste time doing this locally.
It's so tempting to do that.
But you you you need yeah, but you need um the the code path has to be the same, right?
So that Django gives you a dummy cache backend, which is great for local development because it it's it's it exposes the cache, but it's just it doesn't work, it doesn't do anything.
So you can say, is this in the cache?
No, it's not, because the dummy cache never caches anything, and then you can go and hit the database.
So in in development, you even though you need you just use a dummy cache backend.
It's a bit like using the console email backend, you know.
Right, right.
Yeah, yeah, yeah.
Uh indexes, I want to quickly note so well one thing which I think is cool is that starting with 111, you can do this in a meta class on your models uh instead of adding a db index field.
I personally find that doing it through meta is a little more readable and I it can put put more things in there.
Um but what would be so indexes are abused.
Like what's the downside of just indexing everything?
Right?
What time and space.
So it takes so when you when you if you've got an indexed field.
Yeah.
Yeah, indexed field, it will call them in the database table, it will um take longer to write that record to the database if it has to index it at the same time.
So that's time.
So write performance is impaired if you've got an index in place, but also space, because um, you know, it's like a phone book, right?
So the classic example of an index is a phone book where I, you know, I want to look pit someone's phone book, phone number up by their name.
Okay, so instead of going through the list one by one, I can just go all go to the alphabet, look at get to the right place in the alphabet and get the number.
Um but phone books are big and fat, right?
They take up a lot of space on the coffee table under in the hallway under the telephone.
So that's the same problem for every index that you add.
Right, they're not costless.
Yeah, they're not costless.
But to be honest, on balance, are you making actual queries you on this data?
If so, you probably want an index in play.
But until you've started, once you're building before you build your application, you probably haven't designed it well enough to know which um columns you'll actually be querying.
Right, I think that's the key thing is your schema can and will change, and especially once you start indexing schema 1.0, it's that's the wrong approach.
I've done that.
I don't sorry, I don't quite understand.
Like if you you start you start with your basic schema, yeah, you go hog wild with indexes, and then you find out that oh, I want to change the index the change the schema around because my data is different, or I add new features, but then you've got the indexes, and it's too much comp too much, it's premature optimization.
Yeah, I mean, and this this is um you know this this happens um in uh no sql land quite a lot because uh for instance Gouch TB, you have to you you have to create these views which are essentially indexes, and you have to specify them up front.
And so you think, oh, I'm gonna here's here's an it here's an index, I'll create this, or here's a view, I'll create this.
Um and it goes and processes them all, and then you realize that was totally wrong.
And this happens with Elasticsearch as well, because you think I'm gonna search on this, so you create an index, you know, searching with these fields, and then you have to re-index it all because you realize that isn't right, it's quite difficult.
Um but
well, this is the thing with NoSQL, it's it it needs to be used appropriately because when you first start using it, you're like, this is amazing.
Like, I don't need to wear that anywhere.
Yeah, yeah.
But uh so anyway, use Django the debug toolbar.
When you when you've got your application running, you're like, I'm gonna deploy this.
Okay, so go through locally, go through, see what the actual queries are, use the explain to um are these queries sensible?
Put an index in, is is the improved.
And whilst local isn't a proxy for the production, it kind of is in in that it won't tell you the exact numbers, but it will tell you the relative scale.
Yeah, I mean, look at it for sure.
Yeah.
Uh and we mentioned, you know, the other if there's a fourth big area, I would I would say be the front-end assets, which is a Django developer, you probably don't have as much control of.
But you can use tools, for example, you can use Django Compressor, third-party package.
Super package um Carlton maintains.
Well, I just help with.
Um you can use a CDN.
Uh, you know, I there's a whole actually I'll link to it.
Um I don't know how to pronounce AD Asmani, who's at Google, has um a whole free web book on images, and which is really fascinating.
I mean, for example, if you haven't thought about it, you know, you can use easy thumbnails, which is a a package, so that rather than showing the two megabyte version of like let's let's say it's a photo, a user profile photo, and someone can upload a photo, but when you show it on the screen, it's a tiny little thing.
Well, you can have a thumbnail version of it, the full version of it.
You know, these are sort of basic steps that um are really performant.
So just front-end assets in general, and especially if you look at um Google page speed, uh, there's other ones all the major browsers have to evaluate site speed.
They will help you, especially with the front-end assets, to see like your JavaScript is way too big or you have
to uh is your web server configured to cache these, to send the right caching headers to say, look, you know, this this this CSS file, cache it indefinitely.
And one thing that um Django Compressor will give you is a nice um concatenated file, but it has a a silly hash in the in the file name.
So you can cache that forever because if you change your CSS, that hash is different, and so the file name is different.
And so you can configure your web server to say to tell the browsers and to tell the proxy caches out there, caches forever.
Oh, it's
it's fun to do performance stuff, it's just never-ending.
Um the last thing I would say, um, and you can say what you like, Carlton, is uh I find that the Django extensions uh third-party package is very helpful because um it's got a whole bunch of things, but specifically with Shell Plus, which will auto-load models into your shell when you need to drop in.
It also has RunServer Plus.
There's it's sort of a Swiss Army knife of tools.
I find myself using it all the time because whenever I go into the shell, I want the models loaded, and it's just I can I can't live without Django extensions.
Yeah, I'm a big fan of it.
It's um it's got this um, I can't remember the exact command, but it's got this ability to output a picture, a dot, a dot file, which is a graphics format, um, of your models.
And you can drag that into, you can either view that in in uh what's the command line program?
I can't remember, but you could I drag it into OmniGraphel, and then you've got a nice diagram of all your models and the relationships between them, and you're like, hey, you know, I love that.
Oh wow, I'm gonna use that.
I can't remember the commands.
Yeah.
Well, I mean, the hard thing with all these uh your favorite packages and tips is just figuring out the the priority and the curation of them.
Uh this is why, for example, like uh so uh awesome Django is a repo I maintain.
There's a whole bunch of third-party packages, and I'm um I appreciate lots of PRs and issues people put in there, but I don't want it to be a thousand packages long.
Um I'm trying to keep it curated.
Um but when Carlton I mentioned, Django debug toolbar, Django extensions, I would say uh almost every Django site should use those.
I don't have a problem saying that.
It's amongst the packages that I'd pip install without you know really having any concerns.
Yeah, that actually would that be a cool thing to like what's your top, you know, your top five, top ten third-party must-haves if we uh surveyed some talking heads and that that might be a fun thing to do.
Yeah.
I should do that.
Um any last things on performance?
We've we've really hit the the kind of the high points, but uh,
Django probably is probably isn't your um problem.
As long as you're not making 200 database requests on a single thing.
You know, if you do the basics, it's Django probably isn't your bottleneck for you know most web apps.
It's your JavaScript and your front end stuff.
That's that will have more of a an effect.
But you know, if you're pushing, if you're pushing it to, you know, to its you know, if your server is doing something intensive, if it's your Django application that's driving it, then yeah, select related, prefetch related, in make indexing, then caching.
Things like serialization can can cost time, you know, to um rest framework.
If you're using rest framework.
If you're really pushing that to limit serialization, it's like template rendering, it it's an expensive process.
So there are alternate serializing options which you might go for if you were really, you know, driving it.
Make sure your middlewares are optimized.
Um, you know, the the list of
this middleware episode, yeah.
But what did you know, premature optimization is the root of all evil.
Chances are you will get the throughput you need doing the the two or three things which you're eating all the time, rather than you know, worrying about, oh, should I spend a week changing my serializer layer to gain five percent?
Well, yeah, I was gonna say, I mean, 20% of the effort will get you 80% of the way there.
Um, but I feel like I've been saying this a lot recently, but it is so tempting to dive into these small little micro changes that will have an impact and ignore talking to users, you know, changing uh more important things around, but it um feels personally it feels better to endlessly optimize performance.
So I have to watch myself with that.
Well, it's like the equivalent of I mean, you know, some people their busy work is answering email, right?
For some kind of thing.
It is coming.
I'm doing a little I'm doing a performance optimization.
Look, I've got five percent more throughput, but yeah, this thing is called once by a back end worker.
Right.
Totally pointless.
Anyway.
Okay, that's the high points.
As ever, we are at DjangoChat.com uh or chat Django on Twitter for the dyslexic folks out there.
And we'll see you all next week.
Bye bye.
All right, take care, bye bye.