Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

75%
+4 −0
Q&A Handling high frequency requests with cancellations in an ASP.NET Core application

There are two issues here. A performance problem and a correctness problem. The approach you suggest seems like it will help mitigate the performance problem while doing nothing for the correctness...

posted 3y ago by Derek Elkins‭  ·  edited 3y ago by Derek Elkins‭

Answer
#2: Post edited by user avatar Derek Elkins‭ · 2021-12-10T01:52:04Z (about 3 years ago)
Slight typo
  • There are two issues here. A performance problem and a correctness problem. The approach you suggest seems like it will help mitigate the performance problem while doing nothing for the correctness problem.
  • Having some way of cancelling the computation if the result is not going to be needed seems desirable. Even better would be having the computation be incremental so later requests don't need to redo earlier work, but I'll assume that option is off the table for the purposes of this post.
  • Your approach seems a bit over-complicated and has some undesirable aspects. Debouncing adds latency which is undesirable, and it's not clear that it will do anything at all to help. It's not clear that load on the servers is the source of the problem at all. In other words, debouncing could have no effect at all except for turning 1 second of latency into 1.5 seconds of latency.
  • As far as cancellation, a new request for the same logical basket should already be enough indication that any outstanding computations for the same logical basket should be cancelled. It shouldn't be necessary to have the front-end have any notion of "cancelling" a request.
  • This segues into the correctness issues though. Out-of-order responses or even out-of-order requests should not lead to any problems at all. That they do is a significant issue itself. Each distinct request should have a unique identifier^[The identifier can be randomly generated or, if you're hardcore, you could have the identifier be a secure hash of the message contents.] that is passed through end-to-end so that the SPA can correlate responses to requests. This also allows deduplicating (on the server and the client) duplicate requests that might be produced by retries. Another useful thing to include in requests is a local logical time i.e. a very simple case of a [Lamport clock](https://en.wikipedia.org/wiki/Lamport_timestamp). This allows the server to know the ordering of messages so it can easily ignore obsolete messages. This isn't necessary if the server's endpoints are stateless. Both of these are standard techniques in distributed computing. The end-to-end unique identifiers allow turning at-least-once semantics into exactly-once semantics, e.g. as described in section 8 of the [Raft paper](https://raft.github.io/raft.pdf).
  • The above should fix the correctness issues. The approach I suggested does have a bit of statefulness to it (which is a downside), and so the logical times would be useful. That said, they are technically not necessary as the retry logic you'd technically need anyway would resolve it. (The issue is out of order requests [without logical timestamps] could lead to earlier requests cancelling later requests and thus the client never gets a response to their query. However, that could happen just due to network glitches anyway so retry logic is already required.)
  • One potential downside to any approach to cancellation is, depending on how things are configured, the server handling the later requests may different from the server handling the earlier requests. In this case, we either can't/don't cancel the earlier request, or the server instances need to communicate amongst themselves which may be costly. For this scenario, (with the above correctness changes) I would probably only bother to opportunistically cancel obsolete computations locally. However, depending on how the system is setup it may be easy/cheap to coordinate the cancellation.
  • There are two issues here. A performance problem and a correctness problem. The approach you suggest seems like it will help mitigate the performance problem while doing nothing for the correctness problem.
  • Having some way of cancelling the computation if the result is not going to be needed seems desirable. Even better would be having the computation be incremental so later requests don't need to redo earlier work, but I'll assume that option is off the table for the purposes of this post.
  • Your approach seems a bit over-complicated and has some undesirable aspects. Debouncing adds latency which is undesirable, and it's not clear that it will do anything at all to help. It's not clear that load on the servers is the source of the problem at all. In other words, debouncing could have no effect at all except for turning 1 second of latency into 1.5 seconds of latency.
  • As far as cancellation, a new request for the same logical basket should already be enough indication that any outstanding computations for the same logical basket should be cancelled. It shouldn't be necessary to have the front-end have any notion of "cancelling" a request.
  • This segues into the correctness issues though. Out-of-order responses or even out-of-order requests should not lead to any problems at all. That they do is a significant issue itself. Each distinct request should have a unique identifier^[The identifier can be randomly generated or, if you're hardcore, you could have the identifier be a secure hash of the message contents.] that is passed through end-to-end so that the SPA can correlate responses to requests. This also allows deduplicating (on the server and the client) duplicate requests that might be produced by retries. Another useful thing to include in requests is a local logical time i.e. a very simple case of a [Lamport clock](https://en.wikipedia.org/wiki/Lamport_timestamp). This allows the server to know the ordering of messages so it can easily ignore obsolete messages. This isn't necessary if the server's endpoints are stateless. Both of these are standard techniques in distributed computing. The end-to-end unique identifiers allow turning at-least-once semantics into exactly-once semantics, e.g. as described in section 8 of the [Raft paper](https://raft.github.io/raft.pdf).
  • The above should fix the correctness issues. The approach I suggested does have a bit of statefulness to it (which is a downside), and so the logical times would be useful. That said, they are technically not necessary as the retry logic you'd technically need anyway would resolve it. (The issue is out of order requests [without logical timestamps] could lead to earlier requests cancelling later requests and thus the client never gets a response to their query. However, that could happen just due to network glitches anyway so retry logic is already required.)
  • One potential downside to any approach to cancellation is, depending on how things are configured, the server handling the later requests may be different from the server handling the earlier requests. In this case, we either can't/don't cancel the earlier request, or the server instances need to communicate amongst themselves which may be costly. For this scenario, (with the above correctness changes) I would probably only bother to opportunistically cancel obsolete computations locally. However, depending on how the system is setup it may be easy/cheap to coordinate the cancellation.
#1: Initial revision by user avatar Derek Elkins‭ · 2021-12-09T12:59:59Z (about 3 years ago)
There are two issues here. A performance problem and a correctness problem. The approach you suggest seems like it will help mitigate the performance problem while doing nothing for the correctness problem.

Having some way of cancelling the computation if the result is not going to be needed seems desirable. Even better would be having the computation be incremental so later requests don't need to redo earlier work, but I'll assume that option is off the table for the purposes of this post.

Your approach seems a bit over-complicated and has some undesirable aspects. Debouncing adds latency which is undesirable, and it's not clear that it will do anything at all to help. It's not clear that load on the servers is the source of the problem at all. In other words, debouncing could have no effect at all except for turning 1 second of latency into 1.5 seconds of latency.

As far as cancellation, a new request for the same logical basket should already be enough indication that any outstanding computations for the same logical basket should be cancelled. It shouldn't be necessary to have the front-end have any notion of "cancelling" a request.

This segues into the correctness issues though. Out-of-order responses or even out-of-order requests should not lead to any problems at all. That they do is a significant issue itself. Each distinct request should have a unique identifier^[The identifier can be randomly generated or, if you're hardcore, you could have the identifier be a secure hash of the message contents.] that is passed through end-to-end so that the SPA can correlate responses to requests. This also allows deduplicating (on the server and the client) duplicate requests that might be produced by retries. Another useful thing to include in requests is a local logical time i.e. a very simple case of a [Lamport clock](https://en.wikipedia.org/wiki/Lamport_timestamp). This allows the server to know the ordering of messages so it can easily ignore obsolete messages. This isn't necessary if the server's endpoints are stateless. Both of these are standard techniques in distributed computing. The end-to-end unique identifiers allow turning at-least-once semantics into exactly-once semantics, e.g. as described in section 8 of the [Raft paper](https://raft.github.io/raft.pdf).

The above should fix the correctness issues. The approach I suggested does have a bit of statefulness to it (which is a downside), and so the logical times would be useful. That said, they are technically not necessary as the retry logic you'd technically need anyway would resolve it. (The issue is out of order requests [without logical timestamps] could lead to earlier requests cancelling later requests and thus the client never gets a response to their query. However, that could happen just due to network glitches anyway so retry logic is already required.)

One potential downside to any approach to cancellation is, depending on how things are configured, the server handling the later requests may different from the server handling the earlier requests. In this case, we either can't/don't cancel the earlier request, or the server instances need to communicate amongst themselves which may be costly. For this scenario, (with the above correctness changes) I would probably only bother to opportunistically cancel obsolete computations locally. However, depending on how the system is setup it may be easy/cheap to coordinate the cancellation.