MongoDB for Developers with Python Transcripts
Chapter: High-performance MongoDB
Lecture: Adding indexes in mongoenegine
0:04 let's go and see how to do this in MongoEngine.
0:07 I think it's preferable to do this in MongoEngine because that means
0:11 simply pushing your code into production will ensure
0:14 that the database has all the right indexes set up for to operate correctly.
0:19 You theoretically could end up with too many,
0:21 if you have one in code and then you take it out
0:23 but you can always manage that from the shell,
0:26 this way at least the indexes that are required will be there.
0:29 I dropped all the indexes again, let's go back through our questions here
0:33 and see how we're doing.
0:36 It says how many owners, how many cars,
0:38 this is just based on the natural sort however it's in the database
0:41 there's really nothing to do here,
0:44 but this one, find the 10 thousandth car by owner, let's look at that;
0:48 that is going to basically be this name, we'll use test,
0:55 it doesn't really matter what we put here
0:57 if we put explain, this should come back as column scan or something like that,
1:01 yeah, no indexes, okay, so how long did it take to answer that question?
1:06 Find the 10 thousandth owner by name,
1:12 it didn't say by name, I'll go and add by name,
1:16 well that took 300 milliseconds, well that seems bad
1:21 and look we're actually using sorting,
1:24 we're actually using paging skip and limit those types of things here,
1:27 but in order for that to mean anything, we have to sort it,
1:31 it's really the sort that we're running into.
1:34 Maybe I should change this, like so,
1:38 sort like so, we could just put one, I guess it's the way we're sorting it,
1:47 so here you can see down there the sort pattern name is one
1:49 and guess what, we're still doing column scan.
1:53 Any time you want to do a filter by, a greater than, an equality,
1:56 or you want to do a sort, you need an index.
1:59 Let's go over to the owner here, this is the owner class
2:04 and let's add the ability to sort it by name or
2:08 equivalently also do a filter like find exactly by name,
2:12 so we're going to come down here
2:14 we're going to add another thing to this meta section,
2:16 and we're going to add indexes,
2:20 and indexes are a list of indexes,
2:25 now this is going to be simple strings
2:28 or they can be complex subdictionaries,
2:31 for composite indexes or uniqueness constraints, things like that,
2:34 but for name all we need is name.
2:38 Let's run this, first of all, let's go over here
2:41 and notice, if I go to owners and refresh, no name,
2:46 let's run this code, find the 10 thousandth owner by name,
2:52 19 milliseconds, that's pretty good,
2:55 let me run it one more time,
2:57 15 yeah okay, so that seems pretty stable,
3:00 and let's go over here and do a refresh, hey look there's one by name;
3:03 we can see it went from what was that,
3:08 something like 300 milliseconds to 15 milliseconds, so that's good.
3:11 How many cars are owned by the 10 thousandth owner,
3:15 so that's 3 milliseconds, but let's go ahead and have a look at this question anyway.
3:19 How many cars are owned by the 10 thousandth owner,
3:22 so here's this function right here that we're calling
3:25 it doesn't quite fit into a lambda expression, so we put it up here
3:28 so we want to go and find the owner by id,
3:30 that should be indexed right, that should be indexed right there
3:34 because it's the id, the id always says an index,
3:36 and now we are saying the id is in this set,
3:40 so we're doing two queries, but both of them are hitting the id thing,
3:44 so those should both be indexed and 3 milliseconds,
3:47 well that really seems to indicate that that's the case.
3:50 How many owners own the 10 thousandth car, that is right here.
3:54 So we'll go find the car, ask how many owners own it.
3:59 Now this one is interesting, so remember when we're doing this
4:02 basically this in query, let's do a quick print of car id here,
4:11 so if we go back over to this, we say let's go over to the owners
4:17 save your documents, so this is going to be car ids,
4:21 it's going to have an object id of that,
4:26 all right, so run this, zero records, apparently this person owns nothing,
4:33 but notice it's taking 77 milliseconds, we could do our explain again here
4:37 and column scan, yet again, not the most amazing.
4:43 So what we want is we want to have an index on car ids, right
4:48 because column scan, not good,
4:50 I think it's not really telling us in our store example
4:53 but for the find it definitely should be.
4:55 So we can come back to our owner over here,
4:58 let's add also like an index on car_ids,
5:02 If we'd run this once again, just the act of restarting it
5:05 should regenerate the database, how long did it take over here—
5:09 a little late now isn't it, because I did the explain,
5:13 I can look at this one, how many cars,
5:16 how many owners does the 10 thousandth car have,
5:19 66 milliseconds, if we look at it now—
5:22 how many owners own the 10 thousandth car, 1.9 milliseconds,
5:29 so 33 times faster by adding that index, excellent,
5:34 find the 50 thousandth owner by name, that's already done.
5:40 Alright we already have an index on owners name so that goes nice and quick,
5:45 and how is this doing, one millisecond perfect,
5:48 this one is super bad, the cars with expensive service 712 milliseconds,
5:52 alright so here, we're looking at service history
5:56 and then we're navigating that .relationship, that hierarchy,
6:00 with the double underscore, going to the price,
6:02 greater than, less than, equal it doesn't matter,
6:05 we're basically working with this value here, this subdocument.
6:08 Let's go over to the car and make that work,
6:11 now the car doesn't yet have any indexes but it will in a second,
6:14 so what we want to do is represent that here
6:17 and in the the raw way of discussing this with MongoDB
6:21 we use . (dot) not double underscore, so . represents the hierarchy here.
6:25 Let's run that again, notice expensive service, 712,
6:30 cars with expensive service, instead of 712 we have 2.4 milliseconds,
6:39 now notice that first time I ran it there is was a pause,
6:42 the second time it was like immediate,
6:45 and that's because it basically was recreating that index
6:47 and that pause time was how long that index took to create.
6:51 So here we have cars with expensive service,
6:53 now we're getting into something more interesting, look at this one with spark plugs,
6:58 we're querying on two things, we're querying on the history and the service,
7:04 let's actually put this over in the shell so we can look at it.
7:19 I've got to convert this over, do the dots there,
7:23 this is going to be the dollar greater operator, colon, like so,
7:30 all right, so we're comparing that service history.price
7:35 and this one, again because you can't put dots in normal json,
7:39 do the dot here and quotes, and this one is just spark plugs,
7:46 alright, let's run this, okay 22 milliseconds,
7:52 how long is it taking over here— 20 milliseconds,
7:56 so that's actually pretty good and the reason I think it's pretty good is
7:59 we already have an index on this half
8:02 and so it has to just basically sort the result, let's find out.
8:11 Winning plan, index on this one, yes, exactly
8:14 so this one is just going to be crank across there
8:18 but we're going to use at least this index here, this by price
8:22 so that gets part of the query there.
8:25 Now maybe we want to be able to do a query just based on the description
8:30 show me all the spark plugs, well that's a column scan,
8:33 so let's go back and add over here one for the description.
8:40 Now how do I know what goes in this part,
8:44 see I have a service history here, if we actually look at the service record object
8:49 it has a price and description, right
8:52 so we know that that results in this hierarchy of
8:54 service history.price, service history.description.
8:57 If we'd run this again, it will regenerate those and let's go over here
9:01 and run this, and let's see, now we're doing index scan on price,
9:09 what else do we got, rejected plans, okay so we got this and query
9:18 and it looks like we're still using the— yes, oh my goodness,
9:24 how about that for a mistake, comma, so what did that do
9:28 that created, in Python you can wrap these lines and that just created this,
9:33 and obviously, that's not what we want, that comma is super important there.
9:38 So let me go over here and drop this nonsense thing,
9:41 try this again, I can see it's building index right now,
9:47 okay, once again we can explain this, okay great,
9:51 so now we're using price and actually we use the description this time
9:58 and you can see the rejected plan is the one that would have used the price,
10:04 so we're using description, not price,
10:06 and how long does it take to run that query— 7.9 milliseconds, that's better
10:13 but what would be even better still is if we could do
10:16 the description and price as a single thing. How do we do that?
10:22 This gets to be a little trickier, if we look at the query we're running,
10:25 we're first asking for the price and then the description,
10:30 so we can actually create a composite index here as well,
10:35 and we do that by putting a little dictionary, saying fields
10:39 and putting a list of the names of the fields
10:44 and you can bet those go like this,
10:48 now this turns out to be really important, the order that you put them here
10:52 price and the description versus description price, for sorting,
10:56 not so much for matching, run it one more time,
11:00 alright, expensive cars with spark plugs,
11:07 here we go, look at that, less than one millisecond,
11:10 so we added one index, it took it from like 66 milliseconds down to 15,
11:16 and then, we added the description one, it turns out that was a better index
11:21 and it took it from 15 to 9, we added the composite index,
11:24 and we took it from 9 to half a millisecond, a 0.6 milliseconds, that is really cool.
11:31 Notice over here, this got faster, let's go back and look at what that is.
11:36 Load cars, so this is the one we are optimizing
11:40 and what are we doing here— let me wrap this so you can see,
11:43 we're doing a count, okay, we're doing a count
11:46 and so it's basically having the database do all the work
11:48 but there's zero serialization.
11:52 Now in this one, we're actually calling list
11:55 so we're deserializing, we're actually pulling all of those records back
11:59 and let's just go over here and see how many there are,
12:08 well that's not super interesting, to have just one, is it,
12:12 alright, that's good, but let's actually make this just this,
12:23 let's drop this spark plug thing and just see
12:26 how many cars there are with this,
12:30 okay there we go, now we have some data to work with,
12:33 65 thousand cars had 15 thousand dollar service or higher,
12:36 after all, this is a Ferrari dealership, right.
12:39 Now, it turns out it's a really bad idea to pull back that many cars,
12:43 let me stop this, let's limit that to just a thousand here as well.
12:54 Okay, so we're pulling back thousand cars because we're limited to this
13:00 and we're pulling back a thousand cars here.
13:03 But notice, this car name and id versus the entire car
13:08 so let's go over here cars with expensive service, car name and id,
13:13 so notice the time, so to pull back and serialize those thousand records
13:17 took actually a while, so it took one basically a second,
13:21 and if we don't ask for all the other pieces,
13:25 if we just say give me just the make, the model and the id,
13:29 here we're using the only keywords, it says don't pull back the other things
13:34 just give me the these three fields when you create them,
13:37 it makes it basically ten times faster,
13:40 let's turn this down to a 100 and see, maybe get a little more realistic set of data.
13:44 Okay, there we go, a 100 milliseconds down to 14 milliseconds,
13:49 so it turns out that the deserialization step in MongoEngine is a little bit expensive
13:55 so if you like blast a million cars into that list, it's going to take a little bit.
14:01 If we can express like I only want to pull back these items,
14:05 than it turns out to be quite a bit faster,
14:10 in this case not quite faster, but definitely faster.
14:15 Let's round this out here and finish this up.
14:17 Here we're asking for the highly rated, highly priced cars,
14:20 we're asking like hey for all the people that come and spend a lot of money
14:26 how did they feel about it?
14:29 And then also what cars had a low price and also a low rating,
14:33 so maybe we could have just somehow changed our service
14:37 for these sort of cheaper like oil change type people.
14:39 It turns out that that one is quite fast,
14:42 this one we could do some work and fixing one will really fix the other
14:46 so we have this customer rating thing, we probably want to have an index on,
14:52 and we already have one on the price,
14:54 so I think that that's why it's pretty quick actually.
14:57 Go over here, and we don't yet have one on the price, on the rating rather,
15:03 so we can do that and see if things get better,
15:07 not too much, it didn't really make too much of a difference,
15:12 it's probably better to use the price than it is the rating,
15:16 because we're kind of doing that together, so we're also going to go down here
15:19 and have the price and customer rating,
15:21 one of these composite indexes, once again,
15:24 and maybe if we change price one more time,
15:29 rating and price— it doesn't seem like we're getting much better,
15:36 so down here this is about as fast as we can get, 16 milliseconds
15:40 and this is less than one millisecond, so that's really good.
15:44 The final thing is, we are looking for high mileage cars,
15:47 so let's go down here and say find where the mileage of the car
15:51 is greater than 140 thousand miles, do we have an index on that,
15:55 you can bet the answer is no.
15:58 Now we could go to the shell and see that, but no we don't have one,
16:01 so let's go up here and add one more,
16:04 and this is in fact the only index we have here in this thing
16:07 that is on like just plain field, not one of these nested ones like this;
16:13 so maybe we also want to be able to select by year,
16:16 so we could have one for year as well. I'm going to add those in.
16:21 Now this high mileage car goes from a hundred and something milliseconds
16:26 down to six, maybe one more time just to make sure,
16:28 yep, 5, 6, seems pretty stable around there.
16:32 So we've gone and we've added these indexes
16:34 to our models, our MongoEngine documents by adding indexes
16:40 and we can have flat ones like this, or we have these here,
16:48 and we also can have composite ones or richer things,
16:52 if we create a little dictionary and we have fields and things like that.
16:57 Similarly an owner, we didn't have as many things we were after
17:00 but we did want to find them by their name and by car id,
17:03 so we had those two indexes,
17:05 honestly this is just a simpler document than the cars.
17:08 So with these things added here, we can run this one more time
17:11 and see how we're doing that code all runs really quick,
17:14 if we kind of scan through here, there's nothing that stands out like super bad,
17:18 5 milliseconds, half, 18, 6, half, 1, 3, 1, let's say,
17:26 this one, I really wish we could do better,
17:29 it just turns out there is like so many records there
17:32 that if we run that here you can see that the whole thing runs in one millisecond,
17:38 super, super fast, we can't make it any faster than that.
17:41 The slowness is basically the allocation,
17:45 assignment, verification of 100 car objects.
17:48 I'd like to see a little better serialization time out of MongoEngine,
17:53 if you have some part of your code that has to load tons of these things
17:56 and it's super performance critical, you could drop down to PyMongo,
18:00 talk to it directly and probably in the case where you're doing that
18:05 you don't need to pull back many, many objects,
18:07 but also you can see that if we limit what we ask for down here,
18:12 that goes back to 14 miliseconds which is really great,
18:15 here we're looking at a lot of events, this is like 16 thousand
18:21 or no, 65 thousand, that's quite a bit, this one is really fast,
18:25 this one is really fast, so I feel like from an index perspective
18:28 we've done quite a good job, how do we know we're done?
18:32 I guess this is the final question, this has been a bit of a long—
18:35 how do we know we're done with this performance bit?
18:39 We know we're done when all of these numbers come by
18:43 and they're all within reason of what we're willing to take.
18:47 Here I have set this up as these are the explicit queries
18:51 we're going to ask and then we'll just time them,
18:54 like your real application does not work that way.
18:56 How do you know what questions is your applications asking and how long it's taking.
19:01 So you want to set up profiling, so you can come over here
19:05 and definitely google how to do profiling in MongoDB,
19:08 so we can came over here and let's just say, db set profiling level
19:13 and you can use this function to say I'm looking for slow queries
19:18 and to me slow means 10 milliseconds, 20 milliseconds something like that,
19:23 it will generate a table called system.profile and you can just go look in there
19:29 and see what queries are slow, clear it out,
19:33 run your app, see what shows up in there
19:35 add a bunch of indexes, make them fast, clear that table,
19:38 then turn around and run your app again,
19:43 and just until stuff stops showing up in there,
19:46 you can basically find the slowest one, make it faster, clear out the profile
19:51 and just iterate on that process, and that will effectively like gather up
19:55 all of the meaningful queries that your app is going to do,
19:59 and then you can go through the same process here
20:01 to figure out what indexes you need to create.