Performance can be a complex beast - modern web applications have dozens of moving parts that can create performance bottlenecks. ASP.NET Core has many tools to help you. This blog post explores how to apply them effectively to build highly-performant web apps with ASP.NET Core.
Performance may not be the first thing on your mind when building applications. Often the priorities are building what the customer needs and meeting deadlines - performance tuning tends to take a backseat. However, when it comes time to ship apps, development teams end up hustling, trying to get performance to usable levels. Such last-minute performance tuning kills forward momentum and results in long hours with lots of stress for developers. We can do better.
If performance is made a priority during development cycles, issues can be resolved in chunks, thus preventing the death march and a bunch of stress. Performance is also money - it’s well-documented that even small changes in performance can result in driving up user engagement and surprisingly large changes in conversion rates. Major websites have realized how important performance tuning is to the overall user experience. Time is Money drives this point home.
Besides user engagement and developer sanity, app performance is also important for SEO purposes - Google has made site performance one of their criteria in search engine rankings. Performance is equally a concern for internal enterprise apps, as slows apps are a drag on user productivity, which costs money.
Building for Performance
So you get the point: Performance should be a focus right from the start. But performance can be a complex beast. Based on web technology stack and app pipeline layer, there are various performance concerns to deal with. Thankfully, there is some help from application platforms - let’s talk about frontend concerns when building apps with ASP.NET Core.
Frontend performance usually consists of two things. The first thing is reducing the size and number of requests going over the wire between server/browser - there are several ways to cut down on web traffic including bundling, compression, and caching strategies. The second thing is reducing the time the user has to wait around for things to happen. This is often achieved by making use of asynchronous code and preventing blocking actions from blocking the user. ASP.NET Core has built-in features that help developers achieve both of these performance goals.
Measuring Speed
While it’s easy to tell if an application is slow, it’s a lot harder to get usable measurements of performance. There are dozens of different metrics and data flows you can measure that contribute to overall performance. Additionally, variables like network traffic render exact measurements difficult. This doesn’t mean measuring performance is hopeless. As long as you can recognize the variability and limitations of benchmarks, you can still make progress.
When gauging performance, there are two different types of measurements you can take. The first is individual measurements - this is you working your way through an application flow and figuring out how long stuff takes. While you can do this with a stopwatch, there are lots of tools you can use to get more granular measurements.
One popular tool is Miniprofiler - created by the fine folks at Stack Exchange to measure performance on sites like Stack Overflow. It comes as a NuGet Package you can simply install in your application. It measures performance and displays the results in a small overlay on your browser. Miniprofiler looks at the whole stack and makes it easy to spot bottlenecks, duplicate SQL queries, and excessive requests. Here’s a glimpse of Miniprofiler in action:
Chrome DevTools are also popular to examine individual requests in web apps. Besides network request monitoring, Chrome DevTools also come with several built-in performance profilers and audit tools. One of the newer audit tools is on the Audits panel in Chrome DevTools. You can run a battery of different performance tests against your site and Google will give you helpful hints on how you can improve performance.
And of course, there is Fiddler - the de-facto tool to monitor your web app’s network layer. In addition to monitoring network traffic, Fiddler will generate statistics that help you find bottlenecks in your application. It also allows for web session manipulation, HTTP/HTTPS traffic recording and web content debugging from any device. Bottom line: Fiddler is here to help, why not use it?
While measuring individual performance is important, you also need to measure aggregate performance. There are classes of performance issues that only appear while the application is under load. The best way to get data about how your whole application running is to use an Application Performance Monitoring (APM) tool. There are many different APM tools on the market - popular ones include New Relic and Application Insights. Application Insights, being a Microsoft product integrated in the stack, is especially useful for ASP.NET applications.
Because you can always make your applications faster, there is a risk of getting sucked into a rabbit hole with performance tuning. Performance needs to be balanced with other development goals and app features. A good goal for any application is to never make the user wait for more than a second for any action. Optimizations of less than a second seem imperceptible to users, while anything over ten seconds should be immediately remediated. If you can’t get a specific action under a second, one thing to consider is making it asynchronous - give the user back control of the app and notify them when the task is finished.
Performance Tips
Bundling and Minification
In modern web apps, the amount of CSS and JavaScript required to make a webpage functional can be astounding. An easy way to reduce that burden is to minify and bundle all CSS/JavaScript/other static assets. Minification works on normal human readable code files to remove all whitespace and shorten variable names - thus resulting in a much smaller file size. Bundling combines several files into a single resource - thus reducing the overhead of simultaneous connections between server-browser to send lots of files over the wire. Combined, these two tactics vastly enhance client-server communications and speed up web apps. Modern well-architected UI frameworks like Telerik UI for ASP.NET MVC or Telerik UI for ASP.NET Core, powered by Kendo UI, will do many of these performance optimizations automatically.
In ASP.NET web applications, client-side architectures tend to come in two flavors. The first involves doing more server-side and pushing Razor views to the client. You may be using some libraries to provide client-side validations or some Ajax functionality, but JavaScript is the icing on the cake - not the main driver. The second approach is where you use a SPA framework like React, Angular, or Vue to do much more computing on the client side. ASP.NET is happy to step back and allow Web API to serve up data from the server side - you let the SPA frameworks do the heavy lifting with JavaScript in the client browser.
Both of these architectural styles are valid and there are different tools for handling each of them. If you are using a SPA framework or have lots of files to deliver client-side, you should be looking to use a client-side bundling tool like Webpack to package your CSS and JavaScript.
Webpack is a tool that takes a variety of different static assets and bundles them for you. Webpack has many loaders that can process different types of files, and also has advanced features that can help you optimize your assets. One example is tree shaking - Webpack can look through your JavaScript files and remove functions that you aren’t using in your code, resulting in a much smaller bundle.
While Webpack is an amazing tool, it can be a bit of a pain to setup. Each type of client-side asset requires a specific loader and there’s usually several similar looking ones to choose from. To shorten your path to success, using a CLI tool or template may be recommended. Most modern JavaScript frameworks come with CLI tools that can scaffold a basic template - which either abstracts away your Webpack setup (like Angular CLI) or does it for you (like the newly updated Create React App). Regardless, you’ll have a working app with proper bundling in a short amount of time, that includes the best practices for your particular framework.
Microsoft also has some fantastic SPA templates you can use for a simple applications. If you aren’t building a SPA and only have a few JavaScript files, your best bet is to use the BuildBundlerMinifier tool. This tool is a NuGet package you can install and it will bundle your assets at build time. It’s relatively easy to setup - you create a json config file that points to your asset folders and you define the output destination for those assets. Here’s what a sample bundle config file may look like:
CSS Preprocessing
CSS is what styles your web apps. While plain CSS can be powerful, you should consider using a preprocessor like LESS or SASS - it does give you superpowers, while maintaining compatibility. And once you start using a bundling tool, you can use it to make your own custom bundles of CSS. In addition to making it easier to build larger stylesheets, you can use these preprocessors to reduce your CSS package size. Most serious CSS developers end up using a frontend library like Bootstrap, Foundation, or Material Design. You are, however, likely not using all the features and controls in those libraries. If you’re using a preprocessor, you can build your own custom distribution and comment out all the stuff you aren’t using. This reduces your bundle size while allowing you to easily include those controls if you want to use them later.
Compression
While minifying your JavaScript and CSS files will reduce the amount of data going over the wire, that’s not the only place you can save time. Most browsers support GZIP compression, which lets you compress your files before transporting them between server and client. ASP.NET Core will automatically compress certain file types, but not everything - for example, the content of JSON results isn’t compressed. If you’re building a SPA web application, use the Response Compression middleware to get additional compression to save on significant bandwidth.
Caching
Regardless of how small individual network requests get, nothing is faster than not using the network at all and grabbing content out of the cache. Caching is an essential part of the performance enhancement for most web apps, and done well, can lead to a wonderfully optimized user experience. ASP.NET has several different types of caching, depending on what part of the stack you’re trying to optimize and how you want to store your data.
Caching in ASP.NET Core can be divided into two categories. The first category is data caching - mostly used for backend processing. This is where we cache data in memory or a distributed cache, like a Redis or SQL Server instance. This type of caching is appropriate for saving the results of frequently accessed database queries or storing complex calculations.
The second type of cache is the response cache - mostly used client side. Response caching controls the way HTTP network requests are cached by the client’s browser. This type of caching is useful for files that don’t regularly change like JavaScript, images, and CSS. ASP.NET includes middleware and Controller Action attributes that allow you to customize the response cache headers. While response caching can help with certain files, you don’t want to use it everywhere. Requests that require fresh data and requests that rely on user identity are not good candidates for response caching.
Another way to employ response caching is to use a Content Delivery Network (CDN). Powered by cloud infrastructure providers, CDNs specialize in fast delivery of static assets - through distributed nodes across the world. You are essentially delivering content from as close to the user as possible, thus cutting down on network latency.
Many web apps built with ASP.NET rely on third party libraries - and turns out, most of these libraries are already available from popular CDNs. There’s a high likelihood that your user has already downloaded that library from a nearby CDN for another site they’ve visited - why not reuse? This is especially true for common libraries like jQuery or Bootstrap, or Node module dependencies for SPA frameworks. When using a CDN version of third party libraries, you should, however, keep a fallback version in case the CDN location no longer works or you need a fresh copy. You can do this using the script TagHelper in ASP.NET Core - here are some example script tags for common JavaScript libraries:
You can also host your own CDNs. Microsoft Azure, for example, has CDN service that is easy to set up - one you can pair with your web application or Azure storage bucket. AWS and other cloud providers have similar CDN services as well. Bottom line, use CDNs to reduce network latencies and meet the user closer to their geographic locations, thus speeding up your web apps.
Make Your Apps Faster Today
Performance can be a complex beast - modern web applications have dozens of moving parts that can create performance bottlenecks. ASP.NET Core has many tools to help you though. From measurement tools like Miniprofiler to middleware and helpful TagHelpers, there’s no shortage of tricks you can use to make your applications go faster.
If you don’t know where to start, begin with measurement. Get Miniprofiler or Application Insights running on your application and start hunting for bottlenecks. From there, let the data guide you. Find the biggest bottleneck you can fix and get to work. Cheers to faster web apps that delight users.
No comments:
Post a Comment