W3C home > Mailing lists > Public > whatwg@whatwg.org > October 2014

Re: [whatwg] Expose XMLHttpRequest [Fetch?] priority

From: Chad Austin <caustin@gmail.com>
Date: Thu, 9 Oct 2014 12:12:04 -0700
Message-ID: <CA+dRvWgoY3DB6LY=xn73kBc5=8v_iAxoVyT=fG6RtB54Dj-wHw@mail.gmail.com>
To: Ilya Grigorik <igrigorik@gmail.com>
Cc: WHATWG <whatwg@whatwg.org>
Hi Ilya,

This thread has been a bit confusing, partially because I did not have a
deep understanding of HTTP 2's priority semantics and partially because I
feel your responses have been too short and vague to truly answer the use
case described at the top of the thread.  For example, you said "0-7
priority is not sufficient.  Please see the previous proposal.", yet I
don't see any discussion in the previous proposal about why 0-7 is not
sufficient.

In addition, because I'm not sure it's clear, priorities and weights
are *different
concepts*.  Weights are used to allocate resources between streams of
identical priority.  Priorities are used to specify that one request should
be completed before another if possible.  Per HTTP 2, "Streams with the
same parent SHOULD be allocated resources proportionally based on their
weight."  This is not the same thing as priority.  Weights may have value
to others, but they don't have value to the use case below.

OK, now that that's clear, let's have a redo:

*The Use Case*

Our application issues thousands (sometimes tens of thousands) of XHR HTTP
requests to populate a WebGL-rendered 3D scene.

Many of those requests are higher *priority* than others, in that, if
possible, they should be completed *before* expending bandwidth on
lower-priority resources.  Moreover, there is no benefit to having multiple
responses downloading simultaneously: assuming saturated bandwidth, it is
always better here to complete one response before moving to the next.
(Having two half-downloaded textures or meshes is not useful.  Having one
fully-downloaded texture or mesh and one not started yet is useful.)

>From a software development point of view, the ideal JavaScript API would
allow issuing *all* requests simultaneously, indicating their relative
priority to the browser, and let the browser map the desired priority to
the underlying protocol stack.

This goal of this thread is to provide feedback into how said priorities
should be specified.


*Current Browser Behavior*
Browsers have widespread support for two protocols (HTTP and SPDY) and are
likely to support HTTP 2 very soon.  Browsers currently prioritize requests
but there is no content-facing access to said priorities beyond the limited
<script defer> and various iframe tricks.

For basic HTTP connections, browsers maintain a fixed-size connection pool
which is fed by a priority queue.  Requests are issued on sockets in
priority order.

For SPDY, Chrome maps its own internal request priority to SPDY's 3-bit
priorities:
https://code.google.com/p/chromium/codesearch#chromium/src/net/spdy/spdy_http_utils.cc&sq=package:chromium&rcl=1412658928&l=168

Firefox does something similar
http://mxr.mozilla.org/mozilla-central/source/netwerk/protocol/http/SpdyStream3.cpp#370

HTTP 2 will not use a numeric priority but instead allows a partial
ordering between streams:
http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.3

*The HTTP 2 Dependency Tree*

Unlike SPDY, HTTP 2 specifies priorities via a tree of stream
dependencies.  Streams at the root of a tree should be processed before
streams farther down the tree.

However, as discussed on ietf-http-wg [
http://lists.w3.org/Archives/Public/ietf-http-wg/2014OctDec/0059.html ], to
implement true *priority* semantics in HTTP 2, either a linked list of
streams is required [ see
https://github.com/chadaustin/Web-Benchmarks/blob/master/http2/simpriority.py#L72
] or the browser or server needs to guarantee that 1) it will remember
priority state about closed streams "long enough" and 2) children of closed
streams are processed AFTER the closed streams' siblings.  (Once closed, an
HTTP 2 stream is promoted to its parent's priority, which would mix up the
desired prioritization.  I can elaborate if desired.)

The trouble with a linked list is that the HTTP 2 PRIORITY frame costs
O(depth(parent) - depth(stream)) [
https://github.com/chadaustin/Web-Benchmarks/blob/master/http2/simpriority.py#L35
], and, with a linked list, reprioritizing a stream would be O(N), where N
is the number of requests.  Re-prioritizing M requests would be O(M*N).  In
that thread, there is some discussion of using skip lists once the number
of requests grows large, but that seems more like a workaround than a
solution.

It appears the dependency tree model originated from William Chan's
proposal here:
https://insouciant.org/tech/spdy-prioritization-case-study-gmail/

*So what interface should the browser expose?*

Before designing a browser API, a few questions must be answered:

1) Should the priority API support legacy HTTP and SPDY?
2) Should the priority API support future protocols beyond HTTP 2?
3) How likely is it future protocols will support non-dependency-based
priority models.
3) How much work should be placed on application developers?  aka how
closely should the API fit the application domain?

IMO, the answers to the above questions are "yes", "yes", and "possible",
and "better for the API to fit the application domain than for the API to
reflect one possible underlying protocol so that a truer implementation in
non-HTTP-2 protocols is possible".

In that light, I think your proposal [
http://lists.w3.org/Archives/Public/public-whatwg-archive/2014Aug/0081.html
] is not satisfactory.  It requires application bookkeeping to map priority
values to the linked list of XHRs, which:

* in the case of SPDY, the browser must then convert back to 3-bit integers
somehow
* in the case of vanilla HTTP, the browser must convert the dependency tree
back to 32-bit integers.  (or reimplement the browser's own priority queue
as a stream dependency model.  I'd love to get a sense from browser authors
if that's likely to happen.)


*Why not expose priority as a signed 32-bit integer?*
For HTTP, this API is trivial to implement: just plumb the priority value
from Fetch down to the HTTP network stack.

For SPDY, 32 bits would need to be mapped to 3 bits.  Something like
Mozilla's algorithm is likely sufficient:
http://mxr.mozilla.org/mozilla-central/source/netwerk/protocol/http/SpdyStream3.cpp#370

For HTTP 2, mapping a 32-bit priority to an HTTP 2 linked list or skip list
is straightforward and O(lg N):
https://github.com/chadaustin/Web-Benchmarks/blob/master/http2/simpriority.py#L76

The final piece necessary is some calibration against the browser's own
prioritization.  That is, the browser already prioritizes certain resources
- what priority value should they be given?  Or, if you wanted an XHR to
have lower-priority than a certain script tag, you could say something like:

var xhr = new XMLHttpRequest;
// assume large values are higher priority
xhr.fetch.priority = document.getElementById('script').fetch.priority - 1;

*What about exposing weights?*

The only use case I've heard for HTTP 2 stream weights is when you have a
pile of progressive images on a page.  In almost all other cases, it's
better to download some resources sooner rather than have a bunch
partially-downloaded.

Should I write a document with a formal API proposal including rationale or
is this thread sufficient?

Thanks,
Chad


On Wed, Oct 1, 2014 at 9:33 PM, Ilya Grigorik <igrigorik@gmail.com> wrote:

>
>
> On Wed, Oct 1, 2014 at 8:39 PM, Chad Austin <caustin@gmail.com> wrote:
>
>> Weight is actually not what I want.  I want priority.  They're different
>> concepts in that priority implies trumping and weight implies proportional
>> resource allocation.
>>
>> That is, if I make 10 high-priority requests, 20 medium-priority
>> requests, and 30 low-priority requests, I don't want ANY of the
>> low-priority requests to consume any resources until either 1) all
>> higher-priority requests have been serviced or 2) there are spare resources
>> that cannot otherwise be used for higher-priority requests.
>>
>
> And.. you've just defined a three-level deep dependency tree, with weights
> for resources within each group.
>


> As you quoted, "Streams with the same parent SHOULD be allocated resources
>> proportionally based on their weight."  Proportional allocation would be
>> incorrect for this use case.
>>
>
> Proportional within the same level of the tree, and based on assigned
> weights within that level.
>
> ig
>
>
>
>>
>> On Wed, Oct 1, 2014 at 8:19 PM, Ilya Grigorik <igrigorik@gmail.com>
>> wrote:
>>
>>>
>>> On Wed, Oct 1, 2014 at 7:59 PM, Chad Austin <caustin@gmail.com> wrote:
>>>
>>>> I don't see a way to set a priority value in there.  The specific
>>>> wording is "Streams can be prioritized by marking them as dependent on the,
>>>> completion of other streams".
>>>>
>>>> I see that a client can specify the weight of a stream and you can say
>>>> that a stream depends on another stream.  Neither of those are what I
>>>> want.  I simply want to specify priorities of a bunch of requests.  How
>>>> would I do that in HTTP 2.0?
>>>>
>>>
>>> Weight is exactly what you want:
>>> http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.3.2
>>>
>>> "A stream that is not dependent on any other stream is given a stream
>>>    dependency of 0x0.  In other words, the non-existent stream 0 forms
>>>    the root of the tree."
>>>
>>> All dependent streams are allocated an integer weight between 1 to
>>>    256 (inclusive)... Streams with the same parent SHOULD be allocated
>>> resources
>>>    proportionally based on their weight. "
>>>
>>> In other words, if you don't care about dependencies, then don't assign
>>> the parent. Doing so will make all streams children of the root of the tree
>>> (0x0), and from there you can use weights to assign relative priority.
>>>
>>> Hope that makes sense.
>>>
>>> ig
>>>
>>>
>>>
>>>
>>
>>
>> --
>> Chad Austin
>> http://chadaustin.me
>>
>
>


-- 
Chad Austin
http://chadaustin.me
Received on Thursday, 9 October 2014 19:27:25 UTC

This archive was generated by hypermail 2.4.0 : Wednesday, 22 January 2020 17:00:24 UTC