Performance Optimization of .NET-based application

Introduction

In this article, we will look at how to improve performance of .NET-based application and therefore also abp framework-based application.

Performance Optimization

Application overall performance is determined by several factors such as:

  • Execution times of database queries
  • Execution times of application backend code
  • Amount of data that has to be sent to the application frontend and/or inside (micro)services
  • Overall speed and occupancy of hardware, which provides the runtime for an application
  • Network connectivity speed and responsiveness (latency) on end devices (in case of web application or application which consumes REST or other type of API)

In abp framework, all .NET code performance techniques can be used combined with abp framework-specific features.

Database

Keep database version and potential extensions (plugins) updated as much as possible.

However, most performance gains can be achieved with the techniques described below.

Also, keep in mind that the performance of production databases heavily depends on regular maintenance (e.g.: defragmentation of indexes, statistics update,...).

Additional performance gains on database-level can be achieved with multiple replicas of the same database (synchronous or asynchronous replication - decision should be made based on client's requirements). Read-only database requests can be redirected to non-primary instances. These topics are not covered in this part of the documentation, because of dependence on specific database technology and also are not for software developers (but for DB admins). DB admins can establish multiple replicas environment and all described database optimization techniques below should work without any changes (with additional application performance gains).

Caching

To improve performance on the database layer (regardless of database type), it's recommended to use caching wherever possible and safe and as long as possible.

Leverage:

Denormalization techniques and JSON (RDBMS)

Using denormalization techniques can (significantly) reduce querying execution times.
Querying such pieces of data from different parts of application should be read from the location where the query is the most efficient (performance-wise).

Writing the same piece of data multiple times inside different SQL tables or even databases consumes more disk space and requires careful domain logic to insert and update the same data across all locations inside a database (databases) as a transactional unit of work.

Use denormalization techniques in conjunction with JSON capabilities (No-SQL capabilities) of modern RDBMS, which can extend denormalization even further.

Further reading:

Database sharding

The smaller the database, the less data is for querying and this also means lower execution times.

  • In multitenant applications, it is recommended to use per-tenant database or multiple databases per tenant (logging db, data db,...).
    Each tenant database can be hosted inside different hardware performance tier (cost-effectiveness).
  • In a microservice environment, it's recommended to use database-per-microservice or even database-per-tenant-per-microservice
    It is possible to achieve described sharding in abp framework:

    Connection Strings

    Multitenancy

    Entity Framework Core Migrations (Look Using Multiple Databases section for migrations)

Database partitioning (RDBMS)

Most modern RDBMS offers horizontal (and also vertical) partitioning.
When querying performance is not good enough because of large tables, it's recommended to create horizontal partitions.

Database indexing, partitioning, denormalization, and sharding techniques

Database indexing, partitioning, denormalization, and sharding can be used in conjunction.

Entity Framework Core

There are some general recommendations for writing efficient queries using EF Core, which are described here.

When maximum performance is needed (complex queries, very frequent queries,...), using Compiled queries and/or SQL queries can improve performance.

Use ReadOnly Repositories and NoTracking (EF Core feature) for read DB requests (abp v8.0 onwards).

For complex queries when LINQ is not suitable you can use Dapper (but prefer EF Core SQL queries instead of Dapper, because of similar approaches - keep technology stack and libraries as small as possible).

Application layer (backend)

  • Microsoft ASP.NET Core Best Practices covers most topics regarding performance.
    Best Practices is part of Microsoft ASP.NET Core Performance documentation.

  • Usege of Cached Service Providers (ICachedServiceProvider or ITransientCachedServiceProvider) to optimize dependency injection.

  • Using asynchronous code (async/await) wherever possibe and not mixing synchronous and asynchronous code

  • For computationally intensive tasks, which can leverage parallel code use Task Parallel Library (TPL) from Microsoft (available from .NET Framework 4.0 onwards).

  • Reduce memory allocations wherever possible.

  • Build deployment packages in Release mode (and with additional optimization possibilities (AoT,...)).

  • Updated to the latest stable version of .NET (and also abp framework) with latest runtime optimizations. Updating .NET is usually not a difficult task.

Frontend

ASP.NET Core MVC (Razor Pages)

Faster page loading

  • Using Bundling & Minifications feature of abp framework wherever possible
  • Using Static JavaScript Client Proxies
  • Avoid longer loading times with blank pages with minimal server-side rendering and transfer loading on javascript level (ajax) with loading spinners for specific GUI elements which require more time to get data from backend

Faster Javascript execution

Prefer NOT using jQuery (or jQuery-based libraries) in favor of pure modern Javascript code (ECMAScript version) if possible (based on application complexity, development time,...).

Based on potential client's browser limitations (older browsers), check with Can I Use for javascript features availability.

abp framework (Commercial) with Razor Pages frontend option integrates jQuery by default, however, custom application code may not need jQuery.


DevExpress also offers non-jQuery version of DevExtreme javascript suite for years now with noticeable performance gains.

Keep in mind, that NuGet wrappers (for ASP.NET Core) for DevExpress requires jQuery under the hood. Avoid using NuGet wrappers in favor of plain javascript/typescript files included in .cshtml.

Network

This section applies to communication between frontend (MVC, Angular,...) and backend and also backend to databases and/or external API services.

  • Reduce the number of calls via the network as much as possible (e.g. usage of API methods that return an array of entities,...)
  • Use modern compression algorithms (Brotli,...) wherever possible
  • Make calls in parallel if possible (usage of HTTP/2 or newer protocol for frontend-backed communication and parallel calls from application backend to external services)
  • Prefer lighter data formats (e.g. JSON over XML, Protobuf over JSON) for fewer data size transfers and/or faster serialization/deserialization
  • Prefer vector-based data formats for graphics elements instead of raster graphics wherever possible

Network engineers can establish specific caching on network-level to further speed-up application (reduce the number of requests that come to the application backend - API). However, this type of optimization should be applied carefully, because application can start behave unpredictable (wrong responses) even the backend code is written correct.

Optimizing for Production

abp framework documentation about Optimizing Your Application for Production Environments

Engincan Veske 19 weeks ago

Thanks for sharing.

Enis Necipoğlu 18 weeks ago

Nice hints, thanks for the great content!

More from Leon Košak

This member hasn't published any posts yet.