ASP.NET Core 2.1 was released by Microsoft at the end of May, and last week we deployed two consumer-facing applications upgraded to use ASP.NET Core 2.1 to production for the first time.
These applications have now been run in production for an entire weekend of peak traffic, and we’ve seen some great performance improvements – in some cases improving average response times by over 40%.
.NET Core 1.0 was released almost 2 years ago now, in June 2016, and we’ve been using it in some form or another at Just Eat ever since. Early adoption was rather limited amongst our teams, mainly due to the fact that .NET Standard support had not yet filtered through to many of the third-party dependencies we use to build our applications, such as the AWS SDK and NUnit, coupled with some APIs that are available in the full .NET Framework not being available in the first release.
.NET Core 2.0 was subsequently released in August last year, adding many more APIs and features, plus applying feedback Microsoft received since the first release from user feedback for real-world application use-cases.
Since the release of .NET Core 2.0, more and more teams here at Just Eat have been adopting .NET Core for new projects, plus a small number of refactors and/or rewrites of existing applications. At the time of writing, we have over 40 different applications (we call these features here at Just Eat) running in our production environments.
In fact, our internal Just Eat technology radar has recently moved .NET Core from a Trial technology to an Adopt technology.
Since the first preview of .NET Core 2.1 was made available in February, the team I work in had been maintaining a branch of two of our ASP.NET Core 2.0 features. One is a consumer-facing MVC web application that serves HTML pages using Razor, and the other is an MVC API application that acts as an orchestration layer between that web application and a number of our lower-level internal APIs, all of which run on .NET Framework 4.6.x.
A simple illustration of how these applications fit together in our architecture is shown below.
From Previews 1 and 2 to Release Candidate 1, we experimented with various new features and capabilities, as well as finding a few bugs, feature gaps and other changes along the way.
While upgrading from 2.0 to 2.1 is relatively simple if you just follow the migration guide, some of the more interesting additions in .NET Core 2.1 require additional development effort to leverage in your existing application.
One new feature we found particularly interesting was HttpClientFactory. Given that both applications perform many HTTP requests as part of their daily operations, particularly the orchestration API, we felt that using the factory and the underlying
HttpClient changes to use a managed Sockets implementation would give us some good performance improvements.
Another was the new WebApplicationFactory<T> class, which allowed us to delete lots of boilerplate code in our test suite and make our integration tests faster and easier to set up.
Overall the Git patches for the two applications to go from 2.0 to 2.1 were
+199/−194 lines for the web application, and
+246/−297 lines for the API.
Once the final ASP.NET 2.1 release was available (it was even released a few days early with some caveats) and we updated our Amazon Machine Image to have the ASP.NET Core 2.1 Runtime pre-installed, we were ready to deploy the upgrade.
First we deployed the 2.1 build of the API to production as a canary (serving a small percentage of traffic) on Monday 4th June and eagerly awaited the outcome. The morning after showed no errors or degraded performance, so we promoted the canary to 100% of traffic for that feature on the Tuesday morning. Once that was done, we deployed the 2.1 build of the web application to production for another over overnight canary. Again, no detriment was seen in our logs and monitoring, so we promoted that to 100% of user traffic on the morning of the 6th June.
At Just Eat our busiest times for traffic are typically in the evenings, particularly at the weekend, so the real litmus test for the new versions of both applications targeting ASP.NET Core 2.1 would be their first weekend serving requests to hungry users of our UK consumer website.
On Monday 11th June I came into the office eager to pore over the results of the weekends’ traffic, and I was not disappointed with the results at all!
Show Me The Numbers!
The comparisons below are for both applications running ASP.NET Core 2.1 on Sunday 10/06/18 compared to the previous Sunday (03/06/18) where it was running ASP.NET Core 2.0. Sunday was chosen as the basis for comparison (rather than Friday or Saturday) as traffic levels from users was the most similar week-to-week.
The response statistics were collected using statsd from either a middleware or
HttpMessageHandler depending on the context, which are pushed to Graphite and then visualised with Grafana.
CPU statistics are collected separately on our AWS EC2 instances and also pushed to Graphite.
The API averaged
107ms per HTTP GET compared to
141ms (approximately a 24% improvement), with much less deviation.
The overall request rate also is shown to give an indication of the relative load on the API.
CPU usage is also illustrated, which again shows a slight overall improvement. The spike in the graph is where auto-scaling increased the number of EC2 instances in service based on average CPU load.
The web app averaged
130ms per page render compared to
230ms (approximately a 43% improvement) which again shows much less deviation.
The overall request rate also is shown to give an indication of the relative load on the web application.
CPU usage is also illustrated, which again shows a slight overall improvement. The spikes in the graph is where auto-scaling increased the number of EC2 instances in service based on average CPU load.
Scaling occurs later than the previous week as the application instances are now able to handle more load per-box for the same average CPU usage. This gives an additional benefit beyond performance, as it can reduce the overall cost of running the application as the total provisioned CPU required for like-for-like load means that less EC2 instances are required.
The second spike on the CPU graph is caused by raised CPU as the application is installed onto the new EC2 instances coming into service, which skews the CPU average temporarily.
The performance improvements made by Microsoft and the Open Source community in ASP.NET Core and the Core CLR are non-trivial. With minimal code changes you can super-charge your existing ASP.NET Core 2.0 applications by upgrading to ASP.NET Core 2.1. This can reduce your response times to make your users’ experience better, and reduce your CPU utilisation to help make your hosting bills lower too!
About the Author
This blog post was written by Martin Costello, a Senior Engineer at Just Eat, who works on consumer-facing websites and the back-end services that power them.
If you like solving problems at scale, why not check out open positions in Technology at Just Eat?