BDE 4.14.0 Production release
Loading...
Searching...
No Matches
balb_ratelimiter

Detailed Description

Outline

Purpose

Provide a mechanism to limit peak and sustained consumption rates.

Classes

See also
balb_leakybucket

Description

This component provides a mechanism, balb::RateLimiter, that enables clients to monitor and control the use of a resource such that the peak consumption rate and the sustained consumption rate do not exceed their respective configured limits.

The limits on resource consumption rates of a balb::RateLimiter object are configured using a specified peak rate (measured in units/s) along with its time-window, and a specified sustained rate (measured in units/s) along with its time-window. The peak-rate time-window indicates a sliding time period over which the average consumption rate shall not exceed the peak-rate; similarly, the sustained-rate time-window indicates a sliding time period over which the average consumption rate shall not exceed the sustained rate. unit is a generic unit of measurement (e.g., bytes, megabytes, number of messages, packets, liters, clock cycles, etc.).

Internal Model

Internally, a rate limiter (currently) models resource usage using two corresponding balb::LeakyBucket objects, one for limiting peak resource usage and one for limiting sustained resource usage. Each leaky bucket provides an approximation for a moving total, where the configured time window corresponds to the period of the moving total, and that time window multiplied by the corresponding rate indicates the sum that the moving total may not exceed (i.e., the capacity of the leaky bucket). As the units are submitted to a rate limiter, they are added to both the peak and sustained rate moving-totals, and then removed over time at the corresponding configured rate.

Figure 1 illustrates the behavior of a rate limiter during a typical usage scenario using moving-totals:

Fig. 1:
Rp (peak rate) = 2 units/s
Wp (peak-rate time-window) = 2 s
Rs (sustained rate) = 1 units/s
Ws (sustained-rate time-window) = 7 s
Submit 5 Submit 7
| | | | | | | | | | 7|~~~|
12| | 6| | 12| | 6| | 12| | 6|~~~|
11| | 5|~~~| 11| | 5| | 11| | 5|~~~|
10| | Lp-4|~~~| 10| | Lp-4|---| 10| | Lp-4|~~~|
9| | 3|~~~| 9| | 3| | 9|~~~| 3|~~~|
8| | 2|~~~| 8| | 2| | 8|~~~| 2|~~~|
Ls-7|---| 1|~~~| Ls-7|---| 1|~~~| Ls-7|~~~| 1|~~~|
6| | +- -+ 6| | +- -+ 6|~~~| +- -+
5|~~~| 5| | 5|~~~|
4|~~~| 4| | 4|~~~|
3|~~~| 3|~~~| 3|~~~|
2|~~~| 2|~~~| 2|~~~|
1|~~~| 1|~~~| 1|~~~|
+- -+ +- -+ +- -+
Time: t0 t0 + 2s t0 + 2s
Submit 2
| | 7| | | | 7| | | | 7| |
12| | 6| | 12| | 6| | 12| | 6| |
11| | 5| | 11| | 5| | 11| | 5| |
10| | Lp-4|---| 10| | Lp-4|---| 10| | Lp-4|---|
9| | 3|~~~| 9| | 3| | 9| | 3|~~~|
8| | 2|~~~| 8| | 2| | 8| | 2|~~~|
Ls-7|~~~| 1|~~~| Ls-7|---| 1|~~~| Ls-7|~~~| 1|~~~|
6|~~~| +- -+ 6|---| +- -+ 6|~~~| +- -+
5|~~~| 5|~~~| 5|~~~|
4|~~~| 4|~~~| 4|~~~|
3|~~~| 3|~~~| 3|~~~|
2|~~~| 2|~~~| 2|~~~|
1|~~~| 1|~~~| 1|~~~|
+- -+ +- -+ +- -+
Time: t0 + 4s t0 + 6s t0 + 6s

Suppose we have a rate limiter with a peak rate of Rp = 2 units/s, a peak-rate time-window of Wp = 2 s, a sustained rate of Rs = 1 units/s, and a sustained-rate time-window of Ws = 7 s.

This rate limiter maintains a moving-total having a capacity Lp = Rp * Wp = 4 units that controls the peak rate and another moving-total having a capacity Ls = Rs * Ws = 7 units that controls the sustained rate.

Figure 1 shows the following sequence of events:

Monitoring Resource Usage

A balb::LeakyBucket provides methods to both submit units and reserve units for future submission. Submitting a unit indicates that it has been consumed by the entity being modeled, and it is added to the moving-totals tracking both peak and sustained resource usage.

Reserving a unit guarantees that available capacity will be reserved so that unit can be submitted in the future without exceeding the configured limits. Reserved units may be later submitted using the submitReserved method or canceled using the cancelReserved method. Reserved units permanently reside in the two moving-totals of consumed units, resulting in the reduction in the effective capacities of the moving-totals, until the reserved units are canceled or submitted. Reserving units effectively shortens the time-window during which the average sustained and peak rate are enforced. Therefore, the time interval between reserving units and submitting or canceling them should be kept as short as possible. For a practical example of using reserved units, please see balb_reservationguard .

The recommended usage of a rate limiter is to first check whether 1 unit can be added without exceeding the rate limiter's configured limits, and if so, consume the desired amount of the resource. Afterwards, submit the amount of consumed resource to the rate limiter.

Whether submitting more units would exceed the configured limits can be determined using the wouldExceedBandwidth method. The estimated amount of time to wait before 1 more unit will be allowed to be submitted can be determined using the calculateTimeToSubmit method.

Time Synchronization

rate limiter does not utilize an internal timer, so timing must be handled manually. Clients can specify an initial time interval for a rate limiter object at construction or using the reset method. Whenever the state of a rate limiter object needs to be updated, clients must invoke the updateState method specifying the current time interval. Since rate limiter cares only about the elapsed time (not absolute time), the specified time intervals may be relative to any arbitrary time origin, though all of them must refer to the same origin. For the sake of consistency, clients are encouraged to use the unix epoch time (such as the values returned by bdlt::CurrentTime::now).

Usage

This section illustrates the intended use of this component.

Example 1: Controlling Network Traffic Generation

Suppose that we want to send data over a network interface with the load spike limitations explained below:

This is shown in Figure 2 below.

Fig. 2:
^ Rate (Units per second)
| _____ .
| / B \ .
2048|---------------------------/-------\--------Rp (Maximum peak rate)
| __ / \ .
| / \ / A2 \ .
| / A1 \ / \ .
1024|--------/------\ ------/---------------\----Rs (Maximum sustained rate)
| __ / \ / \__.
|__/ \/ \___/ .
| .
--------------------------------------------->
T (seconds)

Notice that we can understand the limitations imposed by the rate-limiter graphically as the maximum area above the respective lines, Rp and Rs, that the usage curve to allowed to achieve. In the example above:

o The area above the sustained rate Rs (e.g., A1 or A2+B) should contain no more than 512 bytes (Rs * Ws).

o The area above the peak rate Rp should contain no more than 128 bytes (Rp * Wp).

Further suppose that we have a function, sendData, that transmits a specified amount of data over that network:

bool sendData(bsl::size_t dataSize);
// Send a specified 'dataSize' amount of data over the network.
// Return 'true' if data was sent successfully and 'false' otherwise.

First, we create a balb::RateLimiter object having a sustained rate of 1024 bytes/s, a sustained-rate time-window of 0.5s (512 bytes / 1024 bytes/s), a peak-rate of 2048 bytes/s, and a peak-rate time-window of 0.0625s (128 bytes / 2048 bytes/s):

bsls::Types::Uint64 sustainedRateLimit = 1024;
bsls::TimeInterval sustainedRateWindow(0.5);
bsls::Types::Uint64 peakRateLimit = 2048;
bsls::TimeInterval peakRateWindow(0.0625);
balb::RateLimiter rateLimiter(sustainedRateLimit,
sustainedRateWindow,
peakRateLimit,
peakRateWindow,
now);
Definition balb_ratelimiter.h:379
Definition bsls_timeinterval.h:301
static bsls::TimeInterval now()
Definition bdlt_currenttime.h:290
unsigned long long Uint64
Definition bsls_types.h:137

Note that the rate limiter does not prevent the rate at any instant from exceeding either the peak-rate or the sustained rate; instead, it prevents the average rate over the peak-rate time-window from exceeding maximum peak-rate and the average rate over the sustained-rate time-window from exceeding the maximum sustained-rate.

Then, we define the size of data to be send, the size of each data chunk, and a counter of data actually sent:

bsl::size_t sizeOfData = 10 * 1024; // in bytes
bsl::size_t chunkSize = 64; // in bytes
bsl::size_t bytesSent = 0;

Now, we send the chunks of data using a loop. For each iteration, we check whether submitting another byte would exceed the rate limiter's bandwidth limits. If not, we send an additional chunk of data and submit the number of bytes sent to the leaky bucket. Note that submit is invoked only after the data has been sent.

while (bytesSent < sizeOfData) {
if (!rateLimiter.wouldExceedBandwidth(now)) {
if (true == sendData(chunkSize)) {
rateLimiter.submit(chunkSize);
bytesSent += chunkSize;
}
}

Finally, if submitting another byte will cause the rate limiter to exceed its bandwidth limits, then we wait until the submission will be allowed by waiting for an amount time returned by the calculateTimeToSubmit method:

else {
bsls::TimeInterval timeToSubmit =
rateLimiter.calculateTimeToSubmit(now);
bsls::Types::Uint64 uS = timeToSubmit.totalMicroseconds() +
(timeToSubmit.nanoseconds() % 1000 ? 1 : 0);
bslmt::ThreadUtil::microSleep(static_cast<int>(uS));
}
}
BSLS_KEYWORD_CONSTEXPR int nanoseconds() const
Definition bsls_timeinterval.h:1348
BSLS_KEYWORD_CONSTEXPR_CPP14 bsls::Types::Int64 totalMicroseconds() const
Definition bsls_timeinterval.h:1397
static void microSleep(int microseconds, int seconds=0)
Definition bslmt_threadutil.h:955

Notice that we wait by putting the thread into a sleep state instead of using busy-waiting to better optimize for multi-threaded applications.