S08E11 55mSeason 8

Episode Summary
In this episode of The Modern .NET Show, Jamie Taylor sits down with Anton Moldovan, founder of NBomber, to explore the crucial world of load testing.
Anton shares his 19 years of experience architecting high-load systems (spanning sports and iGaming domains!) and demystifies how to effectively test your .NET applications before they’re hit with peak traffic. They discuss pragmatic functional programming, blending paradigms for effective solutions, and the importance of testing within your existing tech ecosystem.
NBomber: Discover this open-source .NET load testing tool, designed for flexibility and ease of integration with your current workflows. Learn how its simple lambda-function approach allows you to test any protocol without being locked into a specific DSL.
Load Testing Strategies: From end-to-end user journey simulations to isolated microservice testing, Anton explains how to build a robust testing strategy tailored to your needs.
Chaos Engineering & Load Testing: Find out how to combine load testing with chaos engineering principles to build truly resilient and fault-tolerant systems.
Pragmatic Functional Programming:** Understand Anton’s approach to applying functional principles in real-world .NET development, focusing on value and avoiding unnecessary abstraction.
NBomber Studio: Get a sneak peek at the upcoming private cloud offering, providing a cost-effective and flexible load testing solution.
Episode Transcription
Another thing which I also observed is that there is some benefit to be able to run your load test in your native… using your native platform, libraries, protocol access; those type of things. Because in our case, for example, we use Orleans and it’s a proprietary protocol which doesn’t exist in in Java in Scala language. The same about, almost the same, was about Signal R: Microsoft released SignalR for Java, but the quality of this library was different.
Hey everyone, and welcome back to The Modern .NET Show; the premier .NET podcast, focusing entirely on the knowledge, tools, and frameworks that all .NET developers should have in their toolbox. I’m your host Jamie Taylor, bringing you conversations with the brightest minds in the .NET ecosystem.
Today, we’re joined by Anton Moldovan to talk about load testing, advice for testing strategies, and how NBomber can help you to load test your applications. Are you sure that your application can handle 4 million users at once? Better load test it before you start boasting.
We call this type of test, like, "user journey." Like, end-to-end simulating user journey across entire applications. So end-to-end, end-to-end flow, end-to-end tests. But this type of test they they have some downsides.
Along the way, we talked the different types of testing involved in getting your application for production, the many different ways that NBomber (or other load testing suites) can help you prepare for that, and Anton helps us understand a little more about functional programming.
So let’s sit back, open up a terminal, type in dotnet new podcast and we’ll dive into the core of Modern .NET.
Jamie
Anton
No worries, no worries at all, no worries at all. It’s gonna be a ton of fun. We’re gonna talk about something that I know I don’t get to do a lot of. And I feel like a lot of the average .NET developers probably don’t get to do so much, but it is something super important. I will leave the teaser at that, although it’s not really a teaser because folks have probably read the name of the episode and figured it all out by now.
But before we get on to the topic at hand. I was wondering, could you give the folks who are listening in a bit of an intro about the kind of things that you do, the kind of software stuff that you’re interested in that you like to develop? I know you do a lot of stuff in open source. We had a brief conversation before we hit record talking about open source. So like it feels like it’s a bit of a passion of yours. I wonder if you could just let the folks know.
Anton
Jamie
Anton
You can use these techniques and you basically can blend functional programming with other paradigms in a pragmatic way. This is what it means to me.
Jamie
Anton
Yeah, when I start with functional programming, I learn a lot of different abstractions, but at some point I just realized that the code that I write is so complex and I don’t see big value in it, then I just did like two steps back to the roots of functional programming, and I just found that I just need a few things usually like better matching, pure functions and immutability. This is all I need. And then for some cases I can mix it with object-oriented programming where it makes sense. For some cases I can just write very clean pure logic and cover it by some, let’s say, property based testing. Yeah, it, this makes sense to me.
Jamie
So Let’s say the the example that I usually give is
IAsyncEnumerable, which is a. NET thing, I believe available in both C# and F#, but I know of it from a C# perspective. When that was first announced and released, a lot of the pushback was, "now I have to rewrite all my code to use this?! I can’t believe it. That’s really horrid. Why could why would you release a new thing?!" I’m greatly reducing the online discourse down to that, but that’s what it came to. And it felt very much a case of, "hey, just be pragmatic with it, right? If you don’t want to or you can’t think of a use for this particular thing, don’t do it."Especially with software engineering as a subject, right? We need to be pragmatic because there are so many rules and guidances and all sorts of different advice floating around that sometimes, just like any other engineering practice, we can’t use it all because not all of it fits, right? You wouldn’t necessarily just use a hammer to build a bridge, right?
Anton
Jamie
Now load testing is something that I don’t get to do a lot of. I tend to be quite safe in that the industries that I’ve worked in, and the types of software I’ve written haven’t needed a million concurrent users because I haven’t worked for Netflix. But a lot of people do have that requirement, and most of the time I just kind of… I’m happy with making the system scalable and then going, "hey cloud provider, I’m happy for you to figure it out if you want," and then just hands off, letting the cloud provider do it. But this is obviously for folks who want to test what their system can do and maybe find those limits themselves, right?
Anton
As you mentioned regarding the the Netflix case, back in the day, I believe it was 2017 or 2018. They started designing a new system which was supposed to be (a) stateful system that handles a big massive stream of background events, and also in parallel it handles incoming requests from the users. And this system requires a lot of concurrency, high-availability. I actually wanted to run some PRC for my tech design to to prove that the architecture can handle such a load.
And on that time we were using Microsoft Orleans for keeping the state in memory, and also for the WebSockets we used SignalR to be able to send push updates to the users. So the thing was that on that time I just discovered that, first of all the landscape of load testing was different, and I remember there was JMeter; which is good maybe for basic load testing where you can just click buttons and start bombarding some endpoints.
But for my case I wanted something more flexible, something more close to the code itself, where I can use the code to express my load test scenario, and I remember on that time the Scala Gatling framework was quite popular and to me it it looked as a solid solution. So first of all this solution is GVM-based. and it provides you it gives you the nice DSL to express your your scenario. And at some point I realized that this DSL is actually very good for very basic things to express, but as soon as you wanna do some dirty (?) things: introducing some custom hash tables, dictionaries, while true loops, this type of logic, you need to learn the DSL. I understand like today probably with help of AI, ChatGPT, you can probably ask ChatGPT and it can provide you equivalent in this Gatling DSL; but on that time I just found that I start fighting with the framework. I need to learn this concept, and this is not what I want.
Another thing which I also observed is that there is some benefit to be able to run your load test in your native… using your native platform; libraries, protocol access, those type of things. Because in our case, for example, we use Orleans and its proprietary protocol which doesn’t exist in in Java, in Scala language. The same about… almost the same was about SignalR: Microsoft releases SignalR for Java but the quality of this library was different, and we’ve seen this in our load tests that we were able to open maybe five, 10 web sockets connection and that’s it. Like we reached some state where we see some exception, a weird exception we’re were throwing constantly, but on .NET everything worked as a charm. And I recognize that, "hey man, I need something more close to my platform because I see the client drivers for database, they could behave differently depending on the platform."
And basically this for me were was turning point where I realized I would like to build something for .NET, to have like native .NET load testing tool which I can use, and be able to express my intent. When I started designing, I decided that since I was already in functional programming, sort of camp, and I wanted to have to build a like minimalistic framework with a few abstractions, I don’t want to provide a lot of stuff that need to be learned. And in NBomber, for example, you need to learn mainly two abstraction scenario and step that’s it. Other than this, you can reuse your knowledge. If you know C#, you can express anything. You can use dictionary, if, while loop, it’s just regular code which you write every day.
Also, I was thinking that having native debugger which comes with Visual Studio brings a lot of benefits because now, especially for complex tests, I can iteratively debug some stuff, and be able to fix something more quickly, let’s put in this way. Additionally, I also realized that when tests are growing, when it is not just, you know, pinging simple endpoints, but something more bigger, then you need type safe language. So comparing writing tests in in JavaScript and .NET: it’s day at night. So therefore I just decided that, yeah, it makes sense.
And also the thing which I really like about and NBomber and having native load testing tool is how it integrates in my ecosystem. For example, I am developing my software in Visual Studio or Rider I can easily navigate to my load testing project, click run and be able to run my test just from Visual Studio, because the load tests in NBomber is is just (a) basic test which you do with xUnit or nUnit. So you create a a test project, you have the xUnit method, and within this method you just put the logic of NBomber, that’s it. So you can reuse your knowledge, you can reuse your CI/CD jobs for this. So it plays nicely. That was like, key points which triggered me to to start building NBomber.
Jamie
Like the the point that you made really early on into that about using your… you didn’t use these exact words, but you said around the idea of using your own tech stack, right? Because you mentioned JMeter. I remember very early in my career doing stuff with JMeter—I know I did say that I don’t tend to do a lot of load testing, but when I did need to do load testing, I did use JMeter—and that was inside of a container, because no one on the team knew anything about Java. We just knew that if you Googled enough, you could find the arcane command to start JMeter with the particular parameters we needed. None of us understood the parameters, we just knew that if you ran it and pointed it at your app, you would get a chart at the other end. That was it. That was all we needed to know, right? Which was enough for that moment in time. But then when things went wrong, we had no idea.
So no, I completely agree that we should always be using tools, and frameworks, and techniques within our own ecosystem, because then when things go wrong we have a little bit of knowledge about how to reuse them; and especially your point about because if it’s written in F# you can reuse some of your C# knowledge and some of the C# types because guess what folks? Both languages use the same .NET type system.
Anton
Jamie
The fact that it is such a, on paper, incredibly simple way of working that out and and implementing that: that’s genius.
Anton
Jamie
Like how does, from a ten 10,000 foot view, I know we’ve gone really deep into the weeds and said, basically it’s just a lambda function; but what does that look like for for a developer, right? If I’m looking at some NBomber code, what… do I write like… I’m asking a million questions here. I’m really sorry, but like if I’m using NBomber, am I using F#? And if so, am I diving deep into writing my own lambda function? Or if I’m using NBomber, is it just "call a thing from a thing, and it will give you a chart?" Like where does it sit on that spectrum.
Anton
So first of all, F# it’s not pure functional language. It allows you to do side effects. And you can think about this as a stronger language than than C#, but still what whatever you do in C#, the same you can do with with the F#. Regarding NBomber and lambda function: so NBomber basically provides you the abstractions of scenario which contains the name of scenario and the lambda function it’s a body of your function which will be executed, and within this body you can put whatever you want. But there should be something for composition. Within NBomber you can create steps. These steps could be created within the scenario as additional lambda functions. And a step also has a name, and lambda function, and a body. Basically whatever you put in this body Nbomber will compose together and execute like sequentially, step by step.
After finishing all steps execution within the scenario, it treat this as a like successful iteration and writes, under the hood it writes the calculates metrics, calculate statistics. And if it’s needed, it can voice them to any popular observability platform, DataDog, InflexDB, TimescaleDB, whatever you decide.
Jamie
Anton
NBomber comes with a few things one is InfluxDB, Datadog, TimescaleDB, and I believe Prometheus. If you need something different, you always can implement. There’s about maybe three, four methods which you can implement for saving data into your store. That’s about it. Additionally to this NBomber provides clustering support, you can run your test in distributed mode. And also you have a very, I would say, tunable distribution of load. So there is dynamic load distribution. One of them is for example Zipfun. or multinominal distribution where you can specify, "I have several scenarios. I want that 50% of cases, this scenario should be invoked. But in 20% another one." So you can have this option to specify such type of distributions and to simulate basically any workload.
So when I designed NBomber, I actually always tried to sort of mimic the workloads that I needed to implement or to simulate back in the day. So NBomber is basically based on my experience in building high-loaded systems.
Jamie
Anton
And it means that you’re then more likely to, you know, the the phrase is dog-fooding, right? So you’re you’re more likely to then go back and, "can I can I solve this problem now with what I built? And I will then see," and this is the most important bit, "I will then see the pain points of using this in a production-like setting. So then I can help the user. I can empathize with the user and sympathize with the user and see where I can make my application or my library way better," right?
Anton
Sponsor Message
Today's episode of The Modern .NET Show is brought to you by RJJ Software: strategic technology consulting for ambitious SMEs.
You know me as the host of this podcast, but here's what you might not know: I'm also a former Microsoft MVP who's helped businesses from Formula 1 teams to funded startups transform technology from a cost center into a competitive advantage. At RJJ Software, we specialize in three things that matter to growing businesses:
- AI that actually delivers ROI: not hype, just practical implementations that pay for themselves
- Developer Experience optimization: we've helped teams achieve 99% faster deployments and 3x productivity gains
- Strategic technology decisions: from architecture reviews to fractional CTO services
The difference? We don't just advise. We ensure successful implementation through knowledge transfer to your team.
If you're an SME leader wondering why your technology investments aren't delivering, or you're facing critical decisions about AI, modernization, or team productivity, let's talk.
Visit rjj-software.co.uk/podcast to book a strategic consultation.
Now, let's back to today's episode...
Jamie
Like, let’s say I build a REST endpoint that uses HTTP and I want to measure that, "fantastic. Now I’ve built a GraphQL endpoint that uses HTTP. Fantastic." And now I go ahead and build something with GRPC that I’ve personally have never built anything with GRPC, so I’m just going to assume that I can build anything with it. Can I test all three of those or am I locked just to REST?
Anton
And if you, for example, integrating NBomber with xUnit or nUnit framework, you can use their assert libraries or methods, basically helpers to do assertion, on top of NBomber statistics. And in addition, NBomber will print out threshold results in the in source results in the HTML report. So you will see how many times some of your threshold failed. And, yeah you will have this information.
Jamie
Anton
Jamie
Anton
One effective approach to load testing is simulating real-world user flows that interact with multiple services. And this end-to-end test replicates how users actually use your application, from logging in in a browser to making purchase or performing other key actions. Basically, by executing these flows under the load you can check how your system, microservices, whatever you use perform collectively. This helps identify bottlenecks in the orchestration of services and evaluate system behaviour under realistic conditions.
So the key benefits of this is you understand how servers interact under the load, you uncover integration or orchestration issues. Potentially in your testing also you may test also autoscaling configuration to avoid unexpected overloads. And you validate that the system meets performance expectation from the user perspective. We call this type of test like user journey, like end-to-end simulating user journey across entire application so end-to-end end-to-end flow end-to-end tasks.
But this type of tests, they have some downsides. First, high infrastructure cost, because running such load tests across the entire system typically requires spinning up all microservices, databases, caches, queues and other dependencies. This can be resource intensive, especially if you need to scale components just for the purpose of the test. For big companies, this type of task can lead to significant cost. Another downside of it is a slower feedback loop. Because of the orchestration involves these tests take longer to set up, prepare data and and and run, making them less suitable for fast iterative development cycle. So as a result your developers might only discover performance degradation at the last moment. Basically we recommend using this end-to-end strategy when, I would say, often in preparation for some major big events like Black Friday or monthly releases. The reason to run such tests less frequently is due to their cost. Depending of course on the scale of the company, this test can be expensive to run.
And answering your question about who should write this type of test, usually this this particular this type of test is written by automation QA engineers or performance engineers. Usually it’s not just developers. Usually it’s special QAs, performance folks.
And another, like in addition to this, there is another stream, another approach; we call it "load tests in isolation," or where you testing some microservices in isolation. So while end-to-end testing provides a macro view on the system, it’s equally important to test microservices also in isolation. So this strategy helps you to, like, pinpoint which service may struggle with performance independently of the rest of the system. And this test brings a lot of value.
So key benefits: it’s low low infrastructure costs. These tests are usually very easy to set up and run, even on developer machine. So fast feedback loop; you can quickly identify performance degradation in specific specific service. Isolated tests can be easily integrated in your CI/CD pipeline and treated like a regular unit or integration test, and they identify performance bottlenecks within individual services.
The downside: these tests are not fully representative. So they typically cover only single service. The worst case scenario is when multiple services each performing well in isolation, but fail to handle the expected load when run together in production environment. This is what I’ve seen many times to be honest with you. And this, like local isolated test, this type of test is usually written by developers and not not QA engineers.
Jamie
Anton
Jamie
But if folks are building apps out of several different services, what are the top tips from your perspective for load testing those services together? Is it do things like NBomber and get an idea of what it feels to run this application in production? ‘Cause, like, what are your top tips for that kind of thing? ‘Cause I feel like there’s some gold there that we need to dig a little bit deeper for.
Anton
Usually I suggest to start with end-to-end flows, even if if they maybe will be more expensive, but still they show a lot regarding how your system works, interacts with other services. But then I would say you should try prioritize services that interact directly with that database. Because if these services perform well, usually upstream dependent services will typically also benefit from it and will work also well. It’s usually what I seen from my practice. So since database scalability is is often the main bottleneck, it’s more effective to focus on services that work directly with a database rather than those that depend on the other services in the chain. This is what I usually recommend to companies.
And of course important to be able to easily touch specific microservices, be able to overload specific database. Because I’ve been in situation where the company tried to, you know, develop a new flow based on new database, and in that case it was scylladb. It’s a very fast NoSQL database. And I found that in order for them to run the load test, they need to spin up everything, every microservice, because this is how they have to develop their end-to-end services. They didn’t have any like more like a localized type of test that could just hit this scylladb to validate that workload. So I found that you still should be able to have some solution in your toolbox which will allow you to quickly develop such type of test, just to test specific database, specific flow, be able to run some generator on the background which will produce load for your system. So this is what I suggest usually.
Jamie
Anton
Jamie
Anton
Jamie
Anton
Jamie
Anton
Jamie
Anton
Yeah, so chaos engineering is (a) discipline where we trying to introduce controlled experiments into our system, and basically when I say "experiments" I mean some fault injection or some unexpected condition. And based on this we can understand how our system handles such type of, survives such type of conditions. This discipline, in general, it helps to improve reliability, resilience, and find some hidden vulnerabilities in the code. So I, to be honest, I don’t know how you can build especially for some bi companies, let’s say Netflix or maybe a bit smaller, but still I just don’t know how you can build reliable system without applying chaos engineering.
So and to say this, I want to add that load testing is actually brings a lot of value if you start considering applying chaos engineering, because at some point you will realize that you need to simulate the traffic, and maybe even specific traffic patterns. And this is where a lot of testing will help you.
Usually what I seen is that people start from running the special load on their staging environment, and in parallel they start simulating different experiments. One of them is just killing nodes, shut down some zone, killing network between services or between your Kafka and database, and so on and so forth. Or introducing additional load on CPU or on memory, introduce some restarts of pods in the Kubernetes, and so on and so forth. Based on this experiment they can understand, analyze what went wrong and improve this this. Yeah, that’s in short what I can tell about chaos engineering.
Jamie
But you definitely need to know what happens when this particular service is disabled. Like, "can we do exponential back off and retries, and things like that?" How do you know your retry policy is working if you don’t switch off that microservice to see whether you can retry the the request, right? How do you know that your queue system is working if you’re not putting things onto the queue and then switching off the thing that reads from the queue, right?
Anton
Jamie
Anton
Jamie
What we’ll do is we’ll leave that as an exercise to the listener to go ahead and check out the website, of which I’ve got down as nbomber.com and go and check that out. Get a demo of it, and see if you can, see what you could do with your stuff. See if you can break your stuff. Don’t do it with live things, folks, because you will never hear the end of it, and you may actually get in trouble. But like anything you’re working on in development you pull down locally, maybe throw NBomber at it and see what what it does, right?
Anton
Jamie
Anton
So Yeah, in addition, I suggest to look at integrations on plugins. Also, we’ve been working on the tool, what so-called NBomber Studio. It’s a UI, sort of, UI dashboard for your load tests, which provides you observability for your load test sessions. But also it soon will provide the feature which we call "private cloud", which will allow you to basically create a test and deploy it any cloud. It will be integrated first with Kubernetes. We are almost finishing and polishing this layer. So it basically provides you full end-to-end load testing flow which you can get from the cloud providers. But from the cloud I would say SaaS solutions, but with NBomber Studio you can deploy it within your private environment and run your load test in your environment with unlimited durations, unlimited number of machines, and so on and so forth. So This is quite appealing solution.
Jamie
Anton
So you’re basically receiving the same quality as you could buy in some cloud provider, in cloud I mean SaaS provider, for load testing, But here you you have the privacy, you can run them locally, you can run them in Kubernetes. So it’s a super, super attractive feature.
Jamie
So folks are gonna go check out the website. By the time that this comes out for sure, they’ll be checking out NBomber Studio and taking a look at the private cloud offering. What are some of the other ways to catch up? Like the you know there’s— I did some Googling ahead of time. There is an NBomber blog, which I will have in the show notes. But like if folks are going, "hey, this Anton chap, I wanna know what he’s talking about."
Anton
Jamie
Anton, this has been a fantastic conversation. I say this with everyone, but I genuinely mean it with everyone. I’m walking away from this conversation knowing loads more about load testing as a subject, but also I’m gonna go ahead and try and put Nbomber into at least one of my apps and see what happens. Because I’m very interested to find out.
Anton
Jamie
Wrapping Up
Thank you for listening to this episode of The Modern .NET Show with me, Jamie Taylor. I’d like to thank this episode’s guest for graciously sharing their time, expertise, and knowledge.
Be sure to check out the show notes for a bunch of links to some of the stuff that we covered, and full transcription of the interview. The show notes, as always, can be found at the podcast's website, and there will be a link directly to them in your podcatcher.
And don’t forget to spread the word, leave a rating or review on your podcatcher of choice—head over to dotnetcore.show/review for ways to do that—reach out via our contact page, or join our discord server at dotnetcore.show/discord—all of which are linked in the show notes.
But above all, I hope you have a fantastic rest of your day, and I hope that I’ll see you again, next time for more .NET goodness.
I will see you again real soon. See you later folks.
Useful Links
- NBomber
- MapReduce
- scylladb
- NBomber Studio
- NBomber blog
- Anton on LinkedIn
- Supporting the show:
- Leave a rating or review
- Buy the show a coffee
- Become a patron
- Getting in touch:
- via the contact page
- joining the Discord
- Podcast editing services provided by Matthew Bliss
- Music created by Mono Memory Music, licensed to RJJ Software for use in The Modern .NET Show
- Editing and post-production services for this episode were provided by MB Podcast Services