Subject: Prefer-Push, a HTTP extension.

From: Evert Pot <[email-masked]>
Date: Sun, 18 Nov 2018 23:27:13 -0500

Hi everyone,

We're a group of people collaborating on a HTTP extension. We would like
to introduce a new header that a HTTP client can use to indicate what
resources they would like to have pushed, based on a link relationship.

This might look something like the following request header:

GET /articles
Prefer-Push: item, author

or

GET /articles
Prefer: push="item, author"

We see this feature being especially useful for hypermedia-style APIs.
Many of these types of APIs have some feature to embed resources in
other resources in a way that is ignored by HTTP caches.

The work-in-progress draft can be read here:

<https://github.com/evert/push-please/>

My questions:

1. Would this group be interested in adopting this draft and bringing
   through the standards process?
2. We're having some discussions around which HTTP Header is more
   appropriate. I'm curious if anyone here has any thoughts on that. The
   main drawback is using "Prefer" is that it requires parsing a nested
   format, but it might be more semantically appropriate for HTTP.
3. Our group is mostly divided on one issue: whether this header should
   allow a client to request pushes of arbitrary depth. The cost would
   be increased complexity (thus a higher barrier to entry). I'm curious
   if anyone here has any insights that would help us make this
   decision.

Arbitrary-depth example with a custom format:

  Prefer-Push: item(author, "https://example.org/custom-rel"), icon

Example with S-expression syntax:

  Prefer: push="(item(author \"https://example.org/custom-rel\") icon)"

In each of the above cases the client request the server push:

1. The resource(s) behind the item link-relationship
   a. The resources(s) behind the author relationship (via the "item"
      link-relationship).
   b. The resource(s) behind the "https://example.org/custom-rel" (via
      the "item" link)
2. The resource(s) behind the icon relationship

Unfortunately structured-headers doesn't support data-structures of
arbitrary depth, so if we want arbitrary-depth pushes, we would need to
pick a different format. Very open to suggestions here too.

We intend to have several working implementations of this. For those
interested in discussing, most of our discussion  is happening on a
slack instance (http://slack.httpapis.com channel: #push-please).

Evert et al.

---

From: Mark Nottingham <[email-masked]>
Date: Fri, 23 Nov 2018 17:12:23 +1100

Hi Evert,

Just some personal thoughts. 

Although folks are generally not as optimistic about Push as they used to be, I think there's still some appetite for considering it for non-browsing use cases; as such, this is potentially interesting.

However, it's not clear at all how it'd work to me. Push is effectively hop-by-hop; intermediaries need to make a decision to send it, and that decision needs to be informed. How will a generic intermediary (like a CDN) know what link relations like "item" and "author" are, and how to apply them to a particular connection? 

It's likely that a server (whether an intermediary or an origin) is going to have many, many potential representations of these types to send, so it'll need to have knowledge of the format to be able to get those links out and push the appropriate responses. That doesn't seem generic -- unless you require all of the potentially pushable links to appear in Link headers (which doesn't seem like it would scale very well).

Pushing *all* of the resources -- or even a significant subset -- is likely to cause performance problems of its own, as there will be contention with any other requests made for bandwidth. This is one of the big things we've learned about push in the browsing use case, and I suspect it applies here as well.

Cheers,

> On 19 Nov 2018, at 3:27 pm, Evert Pot <[email-masked]> wrote:
> 
> Hi everyone,
> 

--
Mark Nottingham   https://www.mnot.net/

---

From: Sebastiaan Deckers <[email-masked]>
Date: Sat, 24 Nov 2018 02:24:32 +0800

On Fri, Nov 23, 2018 at 2:16 PM Mark Nottingham <[email-masked]> wrote:

> Although folks are generally not as optimistic about Push as they used to
> be, I think there's still some appetite for considering it for non-browsing
> use cases; as such, this is potentially interesting.
>

FWIW there was some off list discussion on using Prefer Push for browser
resource loading. Allowing resource loaders (native or JavaScript based) to
express interest in specific types of content/destination. See:
https://github.com/evert/push-please/issues/8

> Pushing *all* of the resources -- or even a significant subset -- is
> likely to cause performance problems of its own, as there will be
> contention with any other requests made for bandwidth. This is one of the
> big things we've learned about push in the browsing use case, and I suspect
> it applies here as well.
>

That is the status quo which this spec improves upon. Rather than sending,
for example, all JSON or Atom subdocuments in one concatenated and
unprioritised blob payload, it splits them up and allows use of priorities,
streams, caching, de-duping, etc. Where is it shown that this is likely to
cause performance problems?

---

From: Evert Pot <[email-masked]>
Date: Fri, 23 Nov 2018 17:11:26 -0500

> It's likely that a server (whether an intermediary or an origin) is going to have many, many potential representations of these types to send, so it'll need to have knowledge of the format to be ab…

I potentially see this used in 2 ways:

* Through link headers
* By sending links in response bodies, such as HTML, HAL and ATOM.

The draft, as it's written it is agnostic about where the links appear.

The origin server will generally know which resources to send based on
these links, and link-relationships appearing in Prefer-Push would only
apply to links for which the context uri is the request uri.

In the case of Link headers, intermediaries could cache which links were
available and push optimistically. I agree though if links are in the
body, it's unlikely proxies will parse and push those. I think that's OK.

I'm not entirely sure about the potential scale concern. Is the concern
mainly about the appearance of many Link headers? Intuitively this makes
sense to me. However, for links appearing in response bodies; usually
they are already exist there so we're not really sending any additional
bytes.

> Pushing *all* of the resources -- or even a significant subset -- is likely to cause performance problems of its own, as there will be contention with any other requests made for bandwidth. This is…

I think one of the use-cases we need to test is for example, a JSON
collection that contains "n" items. Each is represented as a resource,
and linked to the collection with an "item" link relationship.

In the past each resource may have been 'embedded' in the response body
of the collection resource, but now we're pushing them. We'll need to
benchmark to find out for what value of "n" this starts breaking.

Frankly, I have no idea even what order of magnitude "n" is in. My hope
is that once we know, we can determine a few best practices for this
feature. It's still possible that we'll find that it's just not possible
to get reasonable performance out of this, but we intend to find out.

Anyway, I'm still curious to learn a bit more about the performance
problems about doing many pushes. Is there mainly an issue with having
too many parallel H2 streams? Could it be solved by limiting the number
of parallel pushes?

If this has to do with the number of streams, I image the issue also
exist with having an similar number of parallel GET requests. But maybe
there's something specific to Push.

Evert

---

From: Andy Green <[email-masked]>
Date: Sat, 24 Nov 2018 07:37:32 +0800

> resources they would like to have pushed, based on a link relationship.

...

> <https://github.com/evert/push-please/>

I have always been a bit puzzled by how PUSH is supposed to be 
beneficial when the server doesn't know what the client has locally 
cached.  Nowadays versioned scripts such as those from 
ajax.googleapis.com are typically told to be cached locally for one year[1]

In the case "self" serves everything and all the assets have similar 
caching policies, after the first visit any PUSH stuff riding on dynamic 
HTML is going to be 99.99% wasted.

The draft doesn't seem to address:

  - why would this be beneficial compared to just sending n pipelined 
GETs on h2, if the client understands it wants n things already?  Both 
ways the return data has to be serialized into individual streams with 
their own headers on a single h2 connection.  With HPACK and n GETs that 
differ only in the request URL, the header sets for each request are 
cheap and you don't have to worry about either magicking up a new format 
to carry the info or "market penetration" of implementations.

The draft says with its method "it's possible for services to push 
subordinate resources as soon as possible" but it doesn't compare it to 
just doing n GETs from the start.  I think you find any advantage is 
hard to measure.  But at least the draft should fairly compare itself to 
the obvious existing way to do it.

  - where does the contemporary knowledge come from at the client about 
the relationships?  From the server, ultimately?  Then this is a bold 
claim...

 > It reduces the number of roundtrips. A client can make a single HTTP 
request and get many responses.

h2 pipelining doesn't work like h1 pipelining.  You can spam the server 
with requests on new streams and most (all?) servers will start to 
process them in parallel while serving of earlier streams is ongoing. 
The server cannot defer at least reading about the new stream starts on 
the network connection because it must not delay hearing about tx credit 
updates or it will deadlock.  So there is a strong reason for servers to 
not delay new stream processing.

-Andy

[1] "The CDN's files are served with CORS and Timing-Allow headers and 
allowed to be cached for 1 year."

https://developers.google.com/speed/libraries/

---

From: Evert Pot <[email-masked]>
Date: Fri, 23 Nov 2018 19:23:10 -0500

> ajax.googleapis.com are typically told to be cached locally for one year[1]

I agree, but for the main case we're trying to solve (embedding vs
pushing), both have this issue. This draft isn't intended to solve that
problem, but the hope is that once Cache Digest[1] lands this _does_
become a viable optimization. (even more so if ETag makes its way back
to key calculations).

> to carry the info or "market penetration" of implementation

One major benefit is if a server knows in advance that a client will
want certain resources, it can optimize for them.

I hope my pseudo-code example illustrates this, but here's the general idea:

function controller(request, response) {

  if (request.preferPush()) {

    response.push( allChildResources() );

  }

}

Generally it's a lot cheaper to generate responses for a group of
resources (based on for example a SELECT query), than it is to generate
responses for each individually.

Plus, for all these pushed responses a service might have done a bunch
of work that doesn't need to be repeated for every response. Consider
that the original request had authentication information, the server
doesn't need to check Authorization headers for every request.

> the obvious existing way to do

A client can only know which links are available and where they point
to, after the initial response came back. After that, I agree, a client
can just do GET requests for every linked resource individually and get
the same performance (not considering the fact that servers can optimize
for groups of similar requests).

> the relationships?  From the server, ultimately?  Then this is a bold

The biggest use-case from my perspective is for hypermedia-style API's,
such as HAL & Siren. In these cases clients generally do have knowledge
of which links might potentially be available, but not where they will
be pointing to.

Solving this for HTTP-services that don't follow this paradigm is out of
scope for this (for me at least).

> not delay new stream processing.

Yes, sorry, this is just to avoid having to wait for the first response
(or subsequent responses in case a bigger part of the graph is
requested), I don't expect it to optimize the case where a client
already knows the target of the links.

Anyway, point taken though. I think the draft needs to do a much better
job addressing this. I also think we need to get more real-world data.

Evert

> https://developers.google.com/speed/libraries/

[1]: https://tools.ietf.org/html/draft-ietf-httpbis-cache-digest-05

---

From: Willy Tarreau <[email-masked]>
Date: Sat, 24 Nov 2018 06:23:00 +0100

Hello,

On Fri, Nov 23, 2018 at 07:23:10PM -0500, Evert Pot wrote:
> to, after the initial response came back.

In fact, with Early Hints (RFC8297) the client can get these info *before*
the initial response, and can even start to preload from different origin
servers if needed. The waste of bandwidth will only be a few tens of bytes
to pass the links and if the client already cached the elements, there is
zero cost here. If the client only cached *some* elements (most likely
case), it will only request the missing ones.

> After that, I agree, a client
(...)

> scope for this (for me at least).

Then maybe it might be worth trying to think about a notion of "bundled"
resources for such use cases, without involving the semantics of a GET
and multiple PUSH in return. This could possibly even allow for better
caching of the whole bundle. I don't know how to do that but we've seen
stuff like "content-type: multipart", maybe this already matches your
use case well where the client asks "give me this and all dependent
resources" ?

> already knows the target of the links.

Please take a look at RFC8297, as it may possibly be interesting for your
use case if your concern is the server's initial response time.

Thanks,
Willy

---

From: Amos Jeffries <[email-masked]>
Date: Sat, 24 Nov 2018 21:08:44 +1300

> they are already exist there so we're not really sending any additional

Linked resources are not always fetched by Browsers. Either because they
are already in the client-side cache, or because they are not necessary
for the part(s) of the page are being displayed or otherwise used. That
is a large source of extra PUSH bytes that can be avoided.

> benchmark to find out for what value of "n" this starts breaking.

I think it is important to distinguish what "the past" actually means.

If by past you mean HTTP/1.0 world. Yes that world benefited from
inlining content as described.

If by past you mean HTTP/1.1 world. That world did not gain nearly as
much benefit from inline objects. Often use of inline prevented
mechanisms like If-* revalidation, pipelining and compression from
achieving best bandwidth reductions.
 The delays waiting for objects earlier in a pipeline still gave a small
edge-case argument for inlining some resources. Contrary to popular myth
a lot of services do not actually benefit from inlining in a purely
HTTP/1.1 world.

HTTP/2 adds multiplexing at the frame level rather than message level so
there is not even those pipeline delay cases existing. Even if one
ignores PUSH completely inlining resources is a negative for performance
in the HTTP/2 world.

PUSH only adds way to further optimize some edge cases where the index
object delivery is less important that the objects it references. So the
sub-objects it references should start delivery immediately and prior to
the index itself.

> to get reasonable performance out of this, but we intend to find out.

AFAIK that 'n' is what the Browser people have been measuring and
testing about these past few years. At lest for their type of traffic.

IMHO that approach is a good one to follow for non-browser agents as
well for their type of traffic before this kind of specification gets
standardized as "The" way to do things. You may find that PUSH is
actually a bad way to go, 'n' is too small to be much use, or just
wildly different from your assumptions.

> too many parallel H2 streams? Could it be solved by limiting the number

I'm not sure of "mainly" is the right word. There are definitely
problems with doing a lot of H2 PUSH'es. From the same causes in general
packet networking that it is a bad idea for TCP to have too many packets
in-transit awaiting ACK.
It causes memory and resource pressure on every hop of the network that
traffic goes through. The more you do the more chance something
somewhere breaks one of them and causes the whole set (or a subset "up
to N") to enter a failure-recovery state.

It is a fine tight-rope style balancing act that both endpoints are
doing with only _guesses_ about what the other endpoint actually needs
to receive. The whole complex issue of message priorities in HTTP/2, for
force some messages to go faster or slower than others is in part to
counter incorrect guessing.

> there's something specific to Push.

One key difference with parallel GET is that the server can be
absolutely sure the client actually wants those objects. With PUSH the
server is guessing and every wrong guess is a waste of bandwidth.

Also, the client message rejecting/closing a PUSH stream is most delayed
when bandwidth limits are slowing traffic down overall, least when
bandwidth is plentiful and fast. So the impact/waste of PUSH is at its
worst during the very times it is most important to avoid wasting bandwidth.

Amos

---

From: Evert Pot <[email-masked]>
Date: Sun, 25 Nov 2018 16:28:17 -0500

> use case well where the client asks "give me this and all dependent

The idea of bundled resources is actually extremely common, and one of
the major reasons we want this draft is actually to try and come up with
ways to avoid this. In the draft I talk about 'embedding', but really
the mechanism you describe is similar.

> use case if your concern is the server's initial response time.

I think using the early hints mechanism is a good idea, and potentially
a solution for a subset of cases.

The cases it doesn't address as well are:

* Situations where a server can optimize for a set of requests /
  responses. (example: give me all members of a collection)
* Situations where using Link headers are undesirable. Most hypermedia-
  style applications will already have a defined format for links.
  Moving the entire response body in headers might be a solution for
  some cases but not all.

Evert