BrandGhost

15-24% FASTER Inserts for MongoDB in C# With One Simple Change

I've been creating MongoDB tutorials fro C# developers so that they can get more familiar with using it. This is because *I* am trying to get more familiar with using it too. During my MongoDB journeys, I wanted to explore if I used BenchmarkDotNet if I could uncover any performance differences. In this video, I'll explain my C# MongoDB benchmarks and benchmarking techniques and... share with you something that I wasn't expecting!
View Transcript
as I started putting my mongod DB tutorials together I became really curious about some performance characteristics because I've never really thought about some differences that we see in mongodb and what I mean is that all of the apis in the mongodb driver that we have as a nougat package can either take a B on document which is the really base form of working with mongodb or we can create our own types to use as entities and I figured look we have these two different paths they got to have some type of different performance right well as usual when putting content together things didn't go quite as planned hi I'm Nick centino and I'm a principal software engineering manager at Microsoft as I mentioned I wanted to collect some Benchmark data about using mongod DB but something I found along the way was that the results were less interesting than I thought but there was something even more interesting that popped up on Twitter and Linkedin and that was using async versus synchronous operations when working with the DB I wanted to test when we're inserting records whether or not we're using a bson document or a custom type what the performance would be like and I might as well test the synchronous versus asynchronous versions while I'm at it in this video I'm going to walk through the benchmark.us themselves and then we're going to look at some of the data that came out of that if you really don't care about any of the code I'm not going to be offended if you want to jump towards the end of this video and just look at the results but I think the codee's really useful because one of the primary points that I want to get across in this video is that if you're curious about something you can go measure it you have all the tools to be able to do it so with that said a quick reminder to check that pin comment for a link to my free newsletter and my courses on dome train and let's look at the code and there it is that's all of our Benchmark code okay but seriously this is just the entry point that we have to be able to run our benchmarks using benchmark.com for any Benchmark classes that we have and run them all but in this particular case I just have this one set of benchmarks for inserting later on I'll probably add some more and do some more investigation but for this we're going to look at these benchmarks here I have them flagged as different things up at the top here to either do a short run or medium run job just depending on the data that I wanted to collect in the time that I was working with but the other important thing to note is that I'm adding this memory diagnoser attribute up here and that way we get some memory information when we're benchmarking the two really important things aside from benchmark.us are test containers and we are using the mongod DB C driver the reason that I'm using test containers is yes of course we're going to be working with a real running database but I wanted to try and normalize this as much as possible I totally get that if I'm running a whole other service on my computer it's not going to be perfect but I figured if I had to go out to the internet to a hosted mongod DB database this is going to be perhaps more realistic or representative but the other thing is that there's other things that I'm not in control of so I just wanted to be able to do an Apples to Apples comparison here so if you haven't seen how to use test containers and you're curious about that you can check out this video that I just put out on how to leverage that especially with with mongodb now I do have a bunch of fields declared up here that we can initialize in our Global setup and that's just so that we're not paying any type of performance penalty up front for doing that inside of the Benchmark methods if I scroll down to the global setup you can see that we're going to run this code here to create the container using test containers and then we start it right here on line 28 this part here is creating a client so that we can connect to our database and then lines 32 through 36 are going to get us our collections that we're interested in working with and you can already see that I'm introducing a bunch of different variations here so we have the Bon document this is sort of that base case that I was interested in and then I figured look I want to have some really simple documents to work with but we have different types that we can use and what I mean by different types is sort of like a record we could use a struct a class so we're dealing with reference or value types I was really curious to see if we notice any patterns across using these in different ways so I do have all the variations here so we have a bon document we do have a record that is a struct we have a record that's a class and then we just have a normal struct in class as well it's almost no extra work to go add this in so I might as well and then we can see if there's going to be anything interesting that comes up if I quickly jump to the end of this file so we can look at the implementations of these so you can see that the definitions of these are all very simple they just have a single name property to work with so nothing fancy nothing hierarchical just a single property that we can set now to scroll through some of the Benchmark code it's going to be a very similar pattern so I'm not going to spend spend a lot of time going in depth on each individual one because the whole point them getting across here is that we're going to be looking at different types they look very similar like they have the same shape of data but they are either reference types or value types records strs or classes now when I put this together the other thing that I mentioned at the beginning of this video is I said hey look there's this other information that popped up on LinkedIn and Twitter and that was talking about the fact that async operations seem like they're even slower than normal synchronous operations for database operations and I thought that was interesting because I've almost defaulted to everything just being a syn AWA and I had this clever idea that one of the things that was potentially slowing it down if that was in fact true is that it was the task right and we have this value task that we can use as well and I figured you know what it's got to be that this was my hunch so I toss that thing into the mix as well so we have variations that are async with a task variations that are asynchronous with a value task and then I also have the synchronous versions as well so this is pretty straightforward you can see that as I scroll through here we go from the Bon docks into the record struct dto that I have here very similar right so insert one async then this one has the value task and then this is the non-async version so on and so forth so just to quickly scroll through you can see that I'm doing all of the types that I had declared at the bottom and to me this was just a really simple way to have coverage across all of these different scenarios and I didn't have any assumptions going into this right I was hopeful that async would be faster because I'm used to using async everywhere now but things on Twitter and Linkedin maybe think otherwise so I was very curious I was hypothesizing the value task might be faster still no idea right a total hunch and then if I had to think about the types that we have to work with whether it's a struct or a class my gut feel was telling me that using a struct would be faster than a class to do the serialization and then I figured if we have a record version of these as well that the record was potentially adding a bit more overhead because there is a little bit more going on behind the scenes and that's just because at compile time we get these other things added onto them so I figured maybe the serialization ends up doing a little bit of extra work because it has more things to look at I had no idea though this is just me speculating so now I'm going to make you sit and watch all of the benchmarks run while I go get a drink and I'm going to come back in about 25 minutes so I'll see you in a little bit okay I'm kidding obviously because I've already written a blog post vote this and have all of the results as well so fortunately for all of us we don't have to go through that um I do have this written up as I said I'll put a link in the description if you want to read through this but this video is covering the same topics as well this just has a little bit more detail as I go to write it out so if I jump over to the benchmarks which are at the end of this article this is what they look like so there's a lot going on on the screen here but the columns that I'm interested in personally are the mean column right here right so that's the First Column of numbers and then the allocated column and that's going to be the memory that's allocated so we can start by looking at the allocated column first just because there's a few patterns that stand out make it pretty easy to talk through and then we can kind of Park that so if we look at the allocated column there's numbers that are roughly around 22.6 2 kiloby and the other numbers that we see coming up are right around 17.78 there's 17.71 so right in that range as well they're not exactly the same in all of the scenarios but very similar and the thing that I wanted to call out is the ones that are about 5 kiloby bigger across the board they are all a sync so to me that's kind of interesting right that's definitely a pattern that shows up it does not matter if it's a sync with a normal task or a sync with a value task in any case they are going to be this number that's around 22.6 2 kiloby so no difference there at all okay now bringing our attention over to the mean column again the things that we're interested in comparing our async versus synchronous as well as the different types that we have to work with and in particular I'm really interested in seeing if there's a struct versus class like reference type kind of difference going on here so let's have us scan through the numbers and see what's up I want to call out first of all that this one right kind of in the middle of everything it's insert one async record class this does have a pretty large error proportional to everything else kind of interesting it might be worth rerunning this Benchmark to see if this is going to maybe be a bit of an outlier um not totally sure but I'm kind of rolling with this one for now it does seem like like it's pretty far on the outside of the runtime here but maybe not so far that it's not realistic so the first comparison that I want to do is looking at async with a normal task versus a value task for performance and if we start scanning top to bottom we can see that for the first two we have the value task taking a little bit longer than the normal task that's one example disproving my theory about a value task being faster in this situation if we go down a little bit lower we can see yet another example like the 67 six here versus 605 micros the value task was slower once again going down even further this one is kind of comparing to the outlier so might be garbage data but this is in my favor but again it might be because this uh standard error is kind of throwing things off so I don't know if I trust this one this one's kind of a moot point I would say a little bit lower we can see value task 605 compared to 642 okay this one is in my favor and going a little bit lower we can see value task yet again a little bit slower in my opinion I don't really see a big Trend here it kind of looks like it's in favor of just using a task over a value task there's a couple of differences that we saw as we scan through this but honestly it looks like if you had to go async probably just using a normal task in this case is showing us that it is a little bit better overall across these scenarios okay now that we want to look at the types cuz I think that's going to be something interesting inter in that was my original Mission with going in this direction for getting benchmarks here I think one of the ways that we can look at this is start looking at uh the groupings of the different things together right so we know that async is going to be slower so maybe to start let's just start with the synchronous versions of them and if we're not really set on that we can jump back to looking at async to see if there's any variations coming up so starting from the top Bon document being inserted this is at 514 micros Bon document is kind of that base type that we have to work with in mongod DB it kind of feels like it's just a dictionary right you can keep adding things it's extensible that way it's not going to be a very strict type in terms of defining your own data transfer object or entity so 514 microc seconds if we go down to insert one for the record struct that's 575 so it's 600 microsc slower interesting I thought that it being a struct would uh automatically kind of catapulted ahead but not the case here um if we go a little bit further insert one record class interestingly enough this is faster than the bson document again I wouldn't have expected this because not only is it not a value type it's a record in my opinion this would have been you know one of the worst case scenarios cuz I mentioned a little bit earlier I hypothesize that a record might have added a little bit of extra bloat in terms of the the extra functionality that they add I know that's done at compile time but I figured when it's serialized it might have to go look at extra data so I was completely wrong about that if we go a little bit lower insert one struct is 521 so that is pretty good that's comparable but if you look at the data that we have here 514 511 521 these are extremely close right the one that was a bit of an outlier was the record struct here um yeah I don't really know if I have an explanation for that compared to the others to me they all seem extremely similar right I mean the data says here don't use a record struct but the other three flavors that we have they're super similar so in this particular case I would say if you're like hey well should I use a bon document or should I use like a a record just a standard record that is a class I would say this is going to depend on your needs right performance- wise they look almost identical right in terms of usability and things like that um I flip-flop between them if I'm kind of building out um what I want my data to look like I might stick with the Bon document if I want to offer a lot more structure if I want to make sure that I can migrate between like my schemas and stuff uh I like using more concrete types that are defined you kind of get flexibility versus strong typing right and this is going to be a tradeoff like I mean in other languages you just have this in general coming up as a trade-off between languages so I I think it's kind of up to you to see what you want but um performance-wise it doesn't look like there's a big difference here okay so performance-wise when I set out to go get these benchmarks I was completely disappointed and didn't have anything stand out in terms of the different types I was really expecting to see once I dug into it that something like a struct would be faster why I mean if you're in the comments on this you might be going well that's stupid here's why it's not like I don't know just a hunch that I had that it would be faster and I figured be document is this really generic Base Class that they have to work with I figured that might be the slowest one to work with so kind of interesting that I was just completely wrong on this but that's exactly why we set out to go measure these things and prove it to ourselves the other thing that was really interesting about this data is that across the board async was slower wait a second we didn't even talk about that we talked about the memory all right let's do this quick so the data that we have on screen is showing in sets of three two a sync and one synchronous version of each document type and that's because async is going to be with task and value task so if we have a quick scroll through the mean column and we'll start with Bon document we can see that the synchronous version is significantly faster than both async versions jumping down to the next set of three record struck at 575 compared to the rest I mean even the fastest async one is uh about 30 micros slower right so again beats out the async versions going down to the next three five is significantly faster than the two async ones above it and yes this one with 722 we keep saying this one's an outlier but even so I mean how much of an outlier is it going to be um I mean if we look at the other examples so far it seems like async is always a little bit slower and I mean we see that already with the 583 compared to 511 so so even with that outlier the one a sync that we have to compare to is in fact slower going down to the next set of three 521 compared to 605 and 642 521 is significantly faster than both of those and then when we get down to the bottom when we start looking at the insert one for class we see 598 compared to the other two this one is interesting this is the only example where we see async in fact being faster so that's interesting I don't have a good explanation for it it's the only outlier 570 compared to 598 592 I mean all kind of in a similarish Range although insert 1 a sync with class is definitely faster than the other two that we see listed right at the bottom but if we take 570s for so insert one async with the class 570 that's the fastest one that we have for classes in general right if we go compare that to the other ones that were fastest in their synchronous versions 570 compared to 521 not as fast 570 compared to 511 up here uh not going to beat that and then 570 compared to 575 I mean it's even even faster than inserting one uh with the record struct right so and then 514 at the very top here for Bon document I mean overall the async version for class even though it's faster than the synchronous version for class by a little bit it's still not really beating out the other examples we see so my personal takeaway from this is that inserting one with a bon document synchronously works pretty good it's probably close to being the fastest that we have on the list except for when we're dealing with a record type inserting one also synchronous these two variations were almost the exact same in terms of runtime and they seem to be faster than almost all the rest on the list with that said though these are slightly contrived right like I don't know how often in your application you're going to be inserting a single document versus doing bulk inserts or bulk writs of other types so what I want to urge you to do is if you're looking at this data going well that means I have to go change my whole app to stop using async and change to using record types and instead like that's not what I'm suggesting at all here I don't want you to have that as the takeaway from this video the takeaway that I want you to have is that if you're curious about what these types of things look like at runtime in terms of memory and their performance I would say Benchmark them right come up with the scenarios that represent what's happening in your application and Benchmark them profile your application figure out where you have hot paths and try to see cool if I have a hot path that looks like this can I write a benchmark for it to exercise that and then can I come up with the variation and compare them like that's what I want you to get as a takeaway from this so I'm doing it because I had a hypothesis but in a real application this is not necessarily going to be what you're doing right I don't know what your application looks like however you got to see all the tools that you could use to go write benchmarks for yourself and if you think benchmarks are cool and you want to see more of them maybe not necessarily with mongod DB you can check them out for collection initializers in this video when it's ready thanks and I'll see you next time

Frequently Asked Questions

What is the main focus of the benchmarks discussed in the video?

In this video, I focus on benchmarking the performance differences between using BSON documents and custom types when inserting records into MongoDB, as well as comparing synchronous versus asynchronous operations.

Why did you choose to use test containers for your benchmarks?

I chose to use test containers to ensure that I was working with a real running database while normalizing the environment as much as possible. This way, I could achieve a more realistic and representative comparison without the interference of other services running on my computer.

What should viewers take away from the benchmark results?

The main takeaway is that if you're curious about performance characteristics in your applications, you should measure them yourself. Use benchmarks to profile your application and identify hot paths, rather than making assumptions based on general trends.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload