Backpressure (2019)
tedinski.com
tedinski.com
> The first law of queues is: Every queue should have a maximum size. Queues must not grow unbounded.
Unfortunately, unbounded resource is often used as a hammer to try preventing other smaller issues, with https://github.com/grpc/grpc/issues/8603 being a perfect example.
Not to mention the second-order effects: if you pretend to drop exactly 0 % of messages, then people will build things that depend on that condition. When the unexpected happens and that condition is violated, bad becomes worse.
By deliberately designing in the option of dropping a reasonable amount of messages, you're creating a more robust system. (As well as a perfect point for a circuit breaker in an emergency.)
Other than in some very specific domains, dropping messages is not a big deal and the design implications of allowing that are far preferable to designing for a fantasy world in which nothing goes wrong ever.
I don't think logging is _some very specific domain_ but I will concede that this might be due to my own biases because of my role.
1) broken software (often their own) that can lock up
2) broken networks that prevent proper dead peer detection, such as by dropping ICMP packets, employing NAT or other stateful firewall rules that drop unknown packets (which happens when a router reboots or otherwise loses or evicts state), etc.
The eternal irony is that these intended robustness hacks invariably end up creating or compounding reliability problems. And people get ridiculously sophisticated with it. For example, rather than add timeouts to each piece of client software, people may an employ a forwarding application proxy (see, e.g., istio on kubernetes) to front requests and implement retries centrally. Now instead of N problems you have 2N or N^2 problems because such architectures invariably end up recapitulating all the sins committed at the IP and TCP level.
This point is particularly relevant:
> Exerting backpressure is why we don’t retry except at the ends of a system. If we’re going to fail, we want to propagate that failure information to where it can be used.
This is an excellent observation and why SC is an end-to-end system which extends all the way to the end user (as opposed to Redis, Kafka, RabbitMQ, STOMP, NSQ and MQTT... which are all designed to be used on the backend in a permissioned environment and do not extend to the end user; they require an additional communication and authentication layer for last-mile delivery). If a message fails to be delivered, then the end user's client is the one which should re-submit the message.
The principle of putting most of the control in the hands of end users has been at the core of SC's design since the beginning.
I think whoever wrote this library has a very different idea of what backpressure is to me:
> For these reasons, SocketCluster exposes a simple API for tracking stream backpressure and it lets you immediately kill streams or groups of streams which are becoming overly congested. SocketCluster lets you measure the backpressure of individual streams within a socket and also the aggregate backpressure of all streams within the socket.
https://socketcluster.io/docs/streams-and-backpressure/
To me, "backpressure" means that if some stage is slow, then it should communicate to upstream stages to slow down, so that queues do not grow. It is an application-level concern in the upstream stages how that slowing-down can be achieved; it might involve coalescing updates, dropping updates that are made irrelevant, reducing sample frequency, etc.
"This got slow so we killed it lol" does not sound useful.
Maybe the kinds of streams you're thinking of are more like 'jobs' (e.g. task queues).
Streams in SC are focused around the pub/sub use case. They are end-to-end (user to user). So if new data is being output by a consumer stream faster than the user's front end can process it, what other option is there but to stop consuming (kill) the stream on the receiver's side?
The front end application could process messages in parallel, but if it did, the backpressure would not build up in the first place. Backpressure only builds up if you make the stream await the processing of each message in a series. If there is no explicit await, then the backpressure is always going to be 0.
End-to-end streams are far easier to manage than the backend-only 'task queues' advocated by Rabbit MQ, Kafka, NSQ, etc. Backend streams are unweildy because you can't give feedback to the user on the front end if something goes wrong, the stream has to process the message no matter what... This is because message queues are completely disconnected from front end applications. Not sure why someone would architect a system like this in the first place. That seems Kafkaesque (pun intended). Isn't it better if the system can catch an issue as soon as it happens and give the originator of the action an opportunity to resolve the issue as soon as possible? The originator of an action is usually best placed to figure out how to handle issues related to the action that they've just performed (e.g. retry, show an error...).
Dealing with realtime data end-to-end is far more straight forward.
Reactive streams and the RSocket protocol are an attempt to build a consistent framework for backpressure, but they don't seem to have spread very far from the niche where they started:
https://www.reactive-streams.org/
APIs for reactive streams even made it into the JDK, but i have never seen anyone use them!