Handling high frequency requests with cancellations in an ASP.NET Core application
I have recently discussed with a friend a performance issue he and his colleagues have encountered in an ASP.NET Core 5 application (a checkout app, microservices architecture).
The problematic flow is related to computing the prices of basket items. The computation may take more than it takes the operator to scan the next item. This may lead the Web application (a SPA) to receive an outdated computed basket, due to out-of-order responses.
The computation itself is hard to optimize (complex discount computations, the algorithm is O(#items), so it can go up to 1 second for large baskets.
Currently, they are exploring optimizing the computation time by replacing the RDMBS with Redis and also identifying and discarding the outdated computed baskets. The solution got pretty complex and has some non-systematic issues.
I am wondering if a simpler solution can be used in this case:
- debounce the calls from the UI - the SPA will not immediately reach to API for basket computation. Instead, it will a period of time (e.g. 500ms) from the last item scanned
- cancellation - this article shows how a canceled browser request can lead to a SQL query cancellation. This requires that awaitable calls (the project already uses async/await almost everywhere) to use CancellationTokens.
This should reduce the strain on the API (no useless computations, since many are discarded anyway) and cancel obsolete computations (if there are some left).
As I have no experience with such performance issues, I am wondering about the possible pitfalls of this proposal.
The only downsides I have identified so far are:
- SPA changes - the app must be changed to introduce debounce and cancellation (none of these are supported now)
- API changes - cancellation tokens are not yet used. However, this does not require API breaking changes and could be easily added to the appropriate methods
I do not know if the microservices architecture itself introduces risk in this case since there are multiple instances for each service.