From 0d6f58c54a7af6c8b4e6cd98663eb36ec4e3accc Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 28 Jan 2020 20:49:17 -0500 Subject: Editorial pass & migration to mkdocs. There's a lot in grimoire.ca that I either no longer stand behind or feel pretty weird about having out there. --- wiki/dev/buffers.md | 99 ----------------------------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 wiki/dev/buffers.md (limited to 'wiki/dev/buffers.md') diff --git a/wiki/dev/buffers.md b/wiki/dev/buffers.md deleted file mode 100644 index 62bcad6..0000000 --- a/wiki/dev/buffers.md +++ /dev/null @@ -1,99 +0,0 @@ -# Observations on Buffering - -None of the following is particularly novel, but the reminder has been useful: - -* All buffers exist in one of two states: full (writes outpace reads), or empty - (reads outpace writes). There are no other stable configurations. - -* Throughput on an empty buffer is dominated by the write rate. Throughput on a - full buffer is dominated by the read rate. - -* A full buffer imposes a latency penalty equal to its size in bits, divided by - the read rate in bits per second. An empty buffer imposes (approximately) no - latency penalty. - -The previous three points suggest that **traffic buffers should be measured in -seconds, not in bytes**, and managed accordingly. Less obviously, buffer -management needs to be considerably more sophisticated than the usual "grow -buffer when full, up to some predefined maximum size." - -Point one also implies a rule that I see honoured more in ignorance than in -awareness: **you can't make a full buffer less full by making it bigger**. Size -is not a factor in buffer fullness, only in buffer latency, so adjusting the -size in response to capacity pressure is worse than useless. - -There are only three ways to make a full buffer less full: - -1. Increase the rate at which data exits the buffer. - -2. Slow the rate at which data enters the buffer. - -3. Evict some data from the buffer. - -In actual practice, most full buffers are upstream of some process that's -already going as fast as it can, either because of other design limits or -because of physics. A buffer ahead of disk writing can't drain faster than the -disk can accept data, for example. That leaves options two and three. - -Slowing the rate of arrival usually implies some variety of _back-pressure_ on -the source of the data, to allow upstream processes to match rates with -downstream processes. Over-large buffers delay this process by hiding -back-pressure, and buffer growth will make this problem worse. Often, -back-pressure can happen automatically: failing to read from a socket, for -example, will cause the underlying TCP stack to apply back-pressure to the peer -writing to the socket by delaying TCP-level message acknowledgement. Too often, -I've seen code attempt to suppress these natural forms of back-pressure without -replacing them with anything, leading to systems that fail by surprise when -some other resource – usually memory – runs out. - -Eviction relies on the surrounding environment, and must be part of the -protocol design. Surprisingly, most modern application protocols get very -unhappy when you throw their data away: the network age has not, sadly, brought -about protocols and formats particularly well-designed for distribution. - -If neither back-pressure nor eviction are available, the remaining option is to -fail: either to start dropping data unpredictably, or to cease processing data -entirely as a result of some resource or another running out, or to induce so -much latency that the data is useless by the time it arrives. - ------ - -Some uncategorized thoughts: - -* Some buffers exist to trade latency against the overhead of coordination. A - small buffer in this role will impose more coordination overhead; a large - buffer will impose more latency. - - * These buffers appear where data transits between heterogenous system: for - example, buffering reads from the network for writes to disk. - - * Mismanaged buffers in this role will tend to cause the system to spend - an inordinate proportion of latency and throughput negotiating buffer - sizes and message readiness. - - * A coordination buffer is most useful when _empty_; in the ideal case, the - buffer is large enough to absorb one message's worth of data from the - source, then pass it along to the sink as quickly as possible. - -* Some buffers exist to trade latency against jitter. A small buffer in this - role will expose more jitter to the upstream process. A large buffer in this - role will impose more latency. - - * These tend to appear in _homogenous_ systems with differing throughputs, - or as a consequence of some other design choice. Store-and-forward - switching in networks, for example, implies that switches must buffer at - least one full frame of network data. - - * Mis-managed buffers in this role will _amplify_ rather than smoothing out - jitter. Apparent throughput will be high until the buffer fills, then - change abruptly when full. Upstream processes are likely to throttle - down, causing them to under-deliver if the buffer drains, pushing the - system back to a high-throughput mode. [This problem gets worse the - more buffers are present in a system](http://www.bufferbloat.net). - - * An anti-jitter buffer is most useful when _full_; in exchange for a - latency penalty, sudden changes in throughput will be absorbed by data - in the buffer rather than propagating through to the source or sink. - -* Multimedia people understand this stuff at a deep level. Listen to them when - designing buffers for other applications. -- cgit v1.2.3