You Cannot Solve Performance Problems by Writing Requirements
You Cannot Solve Performance Problems by Writing Requirements
Non-functional requirement: Performance – any web page of this software shall respond in at least 2
seconds, even when 1,000 users are accessing it concurrently.
The preceding sentence just lets everybody (users, testers, developers, architects, managers, and so on)
know that any web page has a target to achieve. This is a good start, but it is not enough. A great
environment for developing and deploying your application is also important. This is where .NET 6 can
help you a lot; especially if you are talking about web apps, ASP.NET Core is considered one of the
fastest options to deliver solutions today.
When it comes to performance, you, as a software architect, should consider the use of the techniques listed in the
following sections together with specific tests to guarantee this non-functional require- ment. It is also
important to mention that ASP.NET Core will help you to use them easily, together with some Platform as a
Service (PaaS) solutions delivered by Microsoft Azure.
Caching is a great technique to avoid time-consuming and redundant queries. For instance, if you are
fetching car models from a database, the number of cars in the database can increase, but the models themselves
will not change. Once you have an application that constantly accesses car models, a good practice is to cache
that information.
It is important to understand that a cache is stored in the backend and that cache is shared by the whole
application (in-memory caching). A point to focus on is that when you are working on a scalable solution,
you can configure a distributed cache using the Azure platform. In fact, ASP.NET provides both in-memory caching
and distributed caching, so you can decide on the one that bests fits your needs. Chapter 2,
Non-Functional Requirements, covers scalability aspects in the Azure platform.
It is also important to mention that caching can happen in the frontend, in proxies along the way to the
server, CDNs, and so on.
When you develop ASP.NET applications, you need to keep in mind that your app needs to be designed for
simultaneous access by many users. Asynchronous programming lets you do this simply, by giving you the
keywords async and await.
The basic concept behind these keywords is that async enables any method to run asynchronously. On
the other hand, await lets you synchronize the call of an asynchronous method without blocking the thread
that is calling it. This easy-to-develop pattern will make your application run without per- formance
bottlenecks and bring better responsiveness. This book will cover more about this subject in Chapter 2,
Non-Functional Requirements.
One very good tip to avoid poor performance is to understand how the Garbage Collector (GC) works.
The GC is the engine that will free memory automatically when you finish using it. There are some very
important aspects of this topic, due to the complexity that the GC has.
Some types of objects are not collected by the GC if you do not dispose of them. The list includes any
object that interacts with I/O, such as files and streaming. If you do not correctly use the C# syntax to
create and destroy this kind of object, you will have memory leaks, which will deteriorate your
application’s performance.
It might be worth noting that this correct approach also ensures the file gets written (it calls FileStream. Flush() to
dispose of its resources gracefully). In the incorrect example, the contents might not even be written to
the file. Even though the preceding practice is mandatory for I/O objects, it is totally recommended that you
keep doing this in all disposable objects. Indeed, using code analyzers in your solutions with warnings as errors will
prevent you from accidentally making these mistakes! This will help the GC and will keep your application
running with the right amount of memory. Depending on the type of object, mistakes here can snowball,
and you could end up with other bad things on a bigger scale, for instance, port/connection exhaustion.
Another important aspect that you need to know about is that the time spent by the GC to collect ob- jects
will interfere with the performance of your app. Because of this, avoid allocating large objects; otherwise,
it can see you always waiting for the GC to finish its task.
One of the most common performance Achilles’ heels is database access. The reason why this is still a big problem
is a lack of attention paid while writing queries or lambda expressions to get information from a database. This
book will cover Entity Framework Core in Chapter 7, Interacting with Data in C#
– Entity Framework Core, but it is important to know what to choose and the correct data information to
read from a database. Filtering columns and rows is imperative for an application that wants to deliver on
performance.
The good thing is that best practices related to caching, asynchronous programming, and object al-
location fit completely into the environment of databases. It is only a matter of choosing the correct
pattern to get better-performing software.