How to Enhance the Performance of .NET Core Applications for Large Responses
Does your .NET Core 3+ API use Newtonsoft.Json and struggle with performance due to large payloads? Explore solutions in this article.
Join the DZone community and get the full member experience.
Join For FreeProblem Statement
Our API/application uses the Newtonsoft.Json serializer on .NET Core 3 or above, and our response payloads are larger in size. How do we use the .NET code properties and settings to improve API performance?
Possible Case Where You Could Have Started Facing the Performance Issue
This issue could have started when you upgraded your API to .NET Core 3.0 or above with the Newtonsoft.Json serializer, or when you created your API with .NET Core 3 or above and using the Newtonsoft.Json serializer.
You would have started noticing the two behaviors below that were not there before and are performance hits for larger payloads greater than 32KB:
- I/O API operations on disk which results in temp data files in a temp directory
- Impact on the application performance matrix
API response time matters for all domains and applications, but if you are in the financial, trading, payment, banking, etc. domains, a small delay introduced is not acceptable.
So, What We Can Do if We Start Facing the Above Issue?
- With a standard developer mindset, we start by looking at the code, run code optimization analysis, and optimize loops. This is fine, and it helps, too, but if the application was already in better shape, then it would not help much.
- Deep dive into the .NET Core and identify why hops to disk got introduced and how we can avoid it and its impact. I have done this for you and am sharing the below analysis which I think would be helpful for you, too.
Analysis
I will discuss the analysis done and the outcome by digging into the .NET Core internals in detail. You can research it further in case you want to do it by yourself with fare ideas after this article.
If You Are Using .NET Core 3 or Above
1. Migrate to System.Text.Json
If you don't have any custom handling with Newtonsoft.Json, are not majorly dependent on it, and don't mind removing the dependency, then this is your best option. Just move to the new System.Text.Json
. It is by default included in the runtime for .NET Core 3.1 and later, so you don't have to import any package to your application.
- It is async-friendly, which means it will directly write to the response stream, without any intermediary buffer or I/O operations.
- No temp file creation
- If you have to make some tweaks or adjustments to move to
System.Text.Json
, those are easy to implement by using custom converters. For a detailed reference, use "Migrate from Newtonsoft.Json to System.Text.Json." It has detailed information about the differences between the two and explains migration.
But if your API is well established, and you see it would require a bigger effort to migrate to System.Text.json
and you can't afford the time and effort yet, then below are your options.
2. Set SuppressOutputFormatterBuffering = true and AllowSynchrounousIO = false
By setting SuppressOutputFormatterBuffering = true
and AllowSynchrounousIO = false
, you would avoid I/O operations for your API calls and no temp file generation from response buffering.
Before 3.0, the Newtonsoft.Json serializer was by default used to read and write (HttpRequestBody
, HttpResponseBody
) synchronously to/from the output stream. This was perfectly fine with the smaller payloads — but with bigger or larger payloads, thread starvation was an issue. To avoid the thread starvation situation, the .NET Core 3.0 framework introduced asynchronous Input/Output (I/O) API operations as default behaviors.
With .NET Core 3.0 and above, synchronous operations are disabled by default (see "ASP.NET 3.0 Core breaking changes"). The code below is the default setting of the AllowSynchrounousIO
Kestrel property. However, this can be overridden by setting its value to true
, which will allow the Synchronous I/O operations.
services.Configure<KestrelServerOption>(options => {
options.AllowSynchronousIO = false;
});
However, just setting the AllowSynchrounousIO = true
wouldn't be enough because before serializing the response payload, there is an output formatter (NewtonsoftJsonOutputFormatter
) that creates the HTTP response body. It is also an asynchronous operation by default and a reason behind the buffering of response to the disc.
Once again, we get an option to suppress the OutputFormatterBuffering
. It can be overridden by setting the SuppressOutputFormatterBuffering
to true
(default is false
) as shown below.
services.AddControllers(options =>
options.SuppressOutputFormatterBuffering = true;
);
2. If You Are Using .NET Core 6 or Above
If you are using .NET Core 6 or above, and you know the maximum limit of your response size, then you get an option to set the threshold (maximum size of response) before starting the buffering to disk when SuppressOutputFormatterBuffering
is not set.
You can set OutputFormatterMemoryBufferThreshold
to any int
value in KB according to your need as shown below (default is 30KB).
Configure<MvcNewtonsoftJsonOptions>( o => {
o.OutputFormatterMemoryBufferThreshold = 100;
}).BuildServiceProvider();
Analysis Conclusion
If the payload is more than 32KB, .NET Core 3.0 or above will by default start buffering the response to disk, and once buffered, it will serialize and send it to the client. This buffering would save the thread starvation, but as I said earlier, increases the I/O operations extensively and will impact the performance.
You have 3 options based on case to case according to the size of the response payload. The first one is the best approach because it comes with the .NET Core inbuilt. The other two are the workarounds provided by the framework. Whatever case you choose, you will feel better with the improved performance.
It was really great to get into the investigation, and it feels good when you learn something new by digging into the ASP.NET Core and extending your learning a bit more.
I think for any future projects, just simply choose System.Text.Json
— moving to it from Newtonsoft would not be easy because there are many differences between them.
Opinions expressed by DZone contributors are their own.
Comments