📜 ⬆️ ⬇️

Parallel data update in ASP.NET Web API

I want to tell you how we organized the background data update during the request to the REST service.

The task is as follows: the system stores user data. The service works in isolation and does not have direct access to the databases with this data. To work, the service must have in its internal database the names and surnames of users. They can be obtained from the current user's Identity at the time of the request. It is required to add or update names during each request. It is advisable to do this in a separate thread so that this work does not affect the execution time of the main query.

Task clarification


In the database of the service we store the names and surnames of users. Clients need them for information about who created or modified the resource.

This data is not systemically significant: if suddenly the necessary records are missing in the database, nothing terrible will happen. Therefore, we do not want to register our background work in ASP using QueueBackgroundWorkItem to make it difficult to overload the application domain.
It is advisable to solve the problem as easy as possible.

For those who want to learn more about background tasks in ASP.NET, I advise you to read a good article about it.

Decision


We have a DbRefresher class that adds or changes user data in the RefreshAsync method.

Our controllers use the attribute Authorize. Add our successor to this class and override the OnAuthorization method:

public override void OnAuthorization(HttpActionContext actionContext) { base.OnAuthorization(actionContext); if (IsAuthorized(actionContext)) DbRefresher.RefreshAsync(actionContext.RequestContext.Principal) .ContinueWith(t => { LogFactory.For<AuthorizeAndRefreshUserAttribute>() .ErrorException("Error occured", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); } 

DbRefresher.RefreshAsync is an asynchronous method that returns a Task object that will continue its execution in another thread. Exit the OnAuthorize method immediately without waiting for the task to complete. In the event of a crash, an error message will be added to the log.

That's all: it remains only to replace the Authorize attribute in controllers with the name of our new attribute. The new attribute returns control immediately after checking the rights of the current user, after which the controller begins its work. The database will be updated in parallel with the preparation of a response by the controller.

Testing


A test that starts several simultaneous requests to the service will help identify possible problems:

 [TestMethod] public void ConcurrentTest() { const int threadCount = 10; var tasks = new Task[threadCount]; for (int i = 0; i < threadCount; i++) { // DoOperations contains several CRUD operations on resources tasks[i] = Task.Factory.StartNew(DoOperations); } Task.WaitAll(tasks); } 

Problems


If any controller action methods are required for the database to knowingly contain the current user data, this approach is not suitable. We'll have to use an explicit DbRefresher.RefreshAsync call in the body of the method.

There may be problems when adding a new user to the database with multiple simultaneous requests. If an attempt is made to add a user to a table with an already existing key, you should catch the primary key violation exception and stop working. Then only one thread will do all the work of updating user data.

Conclusion


This approach has been successfully working in one of our services in Confit for more than a year.
It seems to me simple and elegant. It would be interesting to know the opinion of the community.

Source: https://habr.com/ru/post/439628/