BDE 4.14.0 Production release
|
Provide a mechanism to limit peak and sustained consumption rates.
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.).
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:
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:
t0s
, we submit 5 units. The submitted units are added to the both moving-totals, and as a result the Lp
is exceeded, which means that the average consumption rate over the peak-rate time-window has exceeded the peak rate. Note that we can not submit any more units at this time even though Ls
is not exceeded (the average consumption rate over the sustained-rate time-windows has not exceeded the sustained rate).t0 + 2s
the number of units contained moving-totals are recalculated. As a result, 4 units (Rp * 2 s
) are subtracted from the peak rate moving-total, and 2 units (Rs * 2 s
) are subtracted from the sustained rate moving-total. Now, capacities of both moving-totals are no longer exceeded, so we are free to submit more units. We submit 7 units, causing both Lp
and Ls
to be exceeded.t0 + 4s
, the moving-totals are again updated. The Lp
limit is no longer exceeded. The number of units held by the moving-total tracking sustained rate matches the moving-total's capacity, and this boundary condition imply and no units can be submitted, because submitting any amount of units would cause Ls
to be exceeded.t0 + 6s
, the moving-totals are again updated. Both Lp
and Ls
are no longer exceeded. We submit 2 units. The Lp
limit is not exceeded, but Ls
limit is exceeded.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.
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
).
This section illustrates the intended use of this component.
Suppose that we want to send data over a network interface with the load spike limitations explained below:
Rs
).Wp
).Rp
).This is shown in Figure 2 below.
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:
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):
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:
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.
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:
Notice that we wait by putting the thread into a sleep state instead of using busy-waiting to better optimize for multi-threaded applications.