Re: [whatwg] Questions about the Fetch API

On Mon, Jul 14, 2014 at 8:56 AM, Domenic Denicola <
domenic@domenicdenicola.com> wrote:

> From: whatwg <whatwg-bounces@lists.whatwg.org> on behalf of Domenic
> Denicola <domenic@domenicdenicola.com>
>
> > Of these, 1 seems much nicer, based on my Node.js experience.
>
> To be clearer as to why this is: stream APIs work much better when things
> you write to are represented as writable streams (option 1), instead of
> representing them as functions that accept readable streams (option 2).


Can you explain this in more detail? AFAICT, the fundamental difference
we're talking about here is push vs pull sources. Option 1 is a push model,
where fetch() creates a writable stream that has an underlying sink
(Domenic, did I get that right? I assume you meant sink and not source) of
the request body. And the user code writes to the stream to stream output
to the request body. In contrast, option 2 is a pull model, where fetch()
accepts as input a readable stream and pulls from it as the network is
ready to write the request body out.

I'm willing to believe that the push model might result in easier APIs for
piping. I haven't thought about it. But I want to assert that the push
model has significant downsides in comparison to the pull model. First,
observe that a stream is a reliable (in the network sense, a la TCP),
ordered sequence of data. The reliability and ordering requirement are key
to understanding the performance issues. That effectively transforms a
stream into a FIFO queue. The sooner you push data into a FIFO queue, the
sooner the source has to commit to the ordering of data. Now, observe that
committing to the ordering of data sooner prevents the source from
reordering later. Why might one want to reorder later? Prioritization.
Delaying the binding of data to a stream allows the source to better
prioritize data. For example, let's say that I have a server. I have
multiple clients issuing requests. I have a single backend connection
(database backend, file serving backend, whatever). So I'm multiplexing
requests from multiple clients over a single connection. If I want to
prioritize the order in which I utilize the single backend [connection], I
had best delay the commit of a request blob to the backend connection
stream. It's better for the backend connection stream to *pull* rather than
for the server to *push* to the backend, because it facilitates this
delayed decision making process.

For a real example of the push vs pull model and its relation to
prioritization, I've got an old blog post (
https://insouciant.org/tech/prioritization-only-works-when-theres-pending-data-to-prioritize/)
I wrote before about Google Maps engineers complaining to me that
prioritization didn't work. The root cause of the problem was the push
model and excessive buffering (in the kernel sink, which defeated our user
space prioritization at the source).

Of course, you can simulate a pull model with a push model by applying
backpressure super aggressively, which I discuss later on in that same blog
post (
https://insouciant.org/tech/prioritization-only-works-when-theres-pending-data-to-prioritize/#The_Solution).
By reducing the amount of buffering, you incur more context switches
instead. In the blog post, a context switch is an actual kernel / user
space OS context switch. In a web browser model, it would be a context
switch between the different layers within the browser platform and the
user code, which can get especially expensive in a multiprocess resource
loading model, such as the one that the Chromium browser uses.

There are many other nuances here but I've neglected to mention out of
brevity. Let me know if more details would help.

Cheers.

PS: I document many of my/Chromium's struggles to undo the push model in
our network stack and the resulting payoffs here -
https://insouciant.org/tech/connection-management-in-chromium/#socket_late_binding
.

Received on Monday, 14 July 2014 22:13:29 UTC