WIP: add request throttling logic #6
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I have been reading the Google SRE book and found this chapter on handling server overload very interesting. The whole chapter is worth a read, but the summary is:
The book outlines several overload mitigations in place at Google. Some of these require tighter integration with different parts of the system (standardized request headers, resource utilization reporting, etc), but there are 2 which are relevant to this package and can be implemented without controlling more of the stack:
Deciding to Retrysection later on in the same chapter).Nothing on this branch in final; the interfaces will likely change, but I wanted to gather some initial thoughts and get feedback. My idea is to introduce a new interface:
Throttlerwhich signals when to skip a request. By default, the roundtripper won't use any throttling because it uses memory and cpu so it should be totally optional. A new transport instantiation option function will be created for a consumer to specify a throttler. It doesn't make sense to allow setting a throttler on the context though, so that won't be an option (although maybe there should be a context key to bypass a previously-specified throttler on a per-request basis).My draft default throttler tracks 3 counts over a sliding time window: total requests, number of requests that came back with an "overloaded" response, and the number of retries. Keeping a sum over a sliding window is a little bit tricky. Since every request goes through the throttler, I wanted to avoid memory allocation and mutex contention. My draft solution is the
atomicCountertype incounter.go. It breaks the time window intonbuckets. Increments just perform an atomic increment on the currently active bucket, and "reading" the current count just sums over each bucket, performing an atomic load on each entry. The last piece is atime.Tickerwhich periodically atomically zeroes out the oldest bucket, then atomically moves the index to the new bucket. This trades granularity for performance, which I think is the right choice here.