Friday, December 18, 2015

How to improve performance with ASP.NET Identity and Episerver

I have written a few blog posts now about how to implement ASP.NET Identity in Episerver. If you use the ASP.NET Identity data model out of the box, you could run into some performance issues with large quantity. With a few tricks you could optimize the data model and improve the performance a lot. I have tested this changes with a database that contains more than 400 thousand users. The performance improved with more than 10 times.

When it comes to both performance and security it’s important that the database isn’t running on the same server as the CMS.

Important note! If you already got a database model up and running and apply this changes you would need to do a migration to new database model. Here is some reference on how to fix that if you run into this problem: 


Prerequisite 

The example in this post are based on the code from here: http://sveinaandahl.blogspot.no/2015/08/how-to-integrate-aspnet-identity-with.html

Database model

It was several elements I found during the analyze that could improve the performance, but here are a few easy fixes.

  • It’s important that Datatypes in the database isn’t using  MAX, e.g. NVARCHAR<MAX>. It’s actually better to set a large number. SQL will not index datatype the contains the datatype MAX.
  • When this is fixed. You can add index on selected tables. By doing this the request dropped from 200 milliseconds to 30. 
  • Another effect by adding indexs is that the SQL server will be steady, even if the CMS frontend server gets a lot of request.

Most of this changes can be done through Entity framework. All the changes you want to apply to the data model can implemented in the OnModelCreating function.  Here you can change the datatype and add index types that will be used a lot.


Update Models/Account/ApplicationUser.cs with this code


using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Annotations;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
 
namespace TestSite.Models.Account
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }
 
    public class MyClaims : IdentityUserClaim
    {
    }
 
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("EcfSqlConnection", throwIfV1Schema: false)
        {
            Database.SetInitializer<ApplicationDbContext>(new DropCreateDatabaseIfModelChanges<ApplicationDbContext>());
        }
 
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
 
            //Shorten length on existing datatypes => improve performance
            modelBuilder.Entity<ApplicationUser>().Property(u => u.PhoneNumber).HasMaxLength(20);
            modelBuilder.Entity<ApplicationUser>().Property(u => u.PasswordHash).HasMaxLength(1024);
            modelBuilder.Entity<ApplicationUser>().Property(u => u.SecurityStamp).HasMaxLength(1024);
            modelBuilder.Entity<MyClaims>().Property(u => u.ClaimType).HasMaxLength(512);
            modelBuilder.Entity<MyClaims>().Property(u => u.ClaimValue).HasMaxLength(512);
 
            //Indexing important datatypes. => improve performance
            modelBuilder.Entity<ApplicationUser>().Property(u => u.Email).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));
        }
    }
}

Happy coding!

1 comment:

  1. Very practical advise indeed. Thanks for sharing
    /K

    ReplyDelete