Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request coalescing as a platform primitive? #20

Closed
igrigorik opened this issue Nov 25, 2015 · 14 comments
Closed

Request coalescing as a platform primitive? #20

igrigorik opened this issue Nov 25, 2015 · 14 comments

Comments

@igrigorik
Copy link
Member

Many applications need to report activity, state, and analytics data in response to user interactions and various app-specific events. Such requests are also typically delay-tolerant (as long as the delay is relatively small), because they are not fetching data required to update the UI, etc.

On mobile devices coalescing network access can have significant impact on improving battery life: waking up the radio incurs a lot of overhead regardless of size (due to timeout logic in the controller that keeps the radio active for some period of time), and coalescing multiple requests to fire at once would help amortize that cost and reduce overall energy footprint.

Implementing this kind of coalescing in app-space is hard: it requires explicit coordination between all actors that initiate fetches (third party scripts, iframes, etc); there isn't sufficient information to infer whether radio is active or not -- e.g. multiple apps/pages running on the device can't coordinate effectively.

Putting all of this together, it seems like it would be helpful to provide some form of a signal to the platform that a particular request is delay-tolerant (for some small delay value - e.g. ~60s) and may be coalesced with other requests. In turn, the platform could keep track of such requests, group them into batches, and periodically "flush them".

Perhaps, there should be a flag or attribute on Fetch API that allows us to mark a particular request as delay-tolerant?

This came up because enabling coalescing is one of the goals of Beacon API. But, at the same time, and for best results it should not be restricted to Beacon only. Further, in terms of layering, it seems like Fetch is the right place to define this type of functionality: it can observe all requests from all contexts, coalesce them effectively, flush them, etc.

/cc @annevk @sicking @toddreifsteck

@sicking
Copy link

sicking commented Nov 25, 2015

How much of a delay are we talking about? Delaying a fetch() long enough that the user might leave the page is a problem since the fetch() API provides a response. This is why sendBeacon doesn't return a Promise.

@sicking
Copy link

sicking commented Nov 25, 2015

I guess one option would be that if fetch(uri, { allowDelay: true }) is called, then the function would not return a promise at all. Or would return a promise which immediately resolves to undefined independently of if the request is actually delayed or not.

@igrigorik
Copy link
Member Author

Well, today when you issue a fetch and get back a Promise, there are no guarantees that said fetch will complete in time either: request is mid-flight, something triggers page unload, and outstanding requests are cancelled. The coalescing flag doesn't change this, it just provides a signal that such request is delay-tolerant and may be held for a short while before going through the same steps.

I guess defining "short while" is the key here. We want this to be flexible though.. If the network interface is active then send immediately; otherwise queue the request until next time interface is active or ~60s passes. If user's battery is low, the ~60s interval may be increased to conserve energy. </hand waving>

The difference with beacon, as you pointed out, is that there is also no resolve callbacks and beacon requests are not cancelled as part of unload (which is why we restrict them to be small, etc). These bits are the unique properties of Beacon API.. coalescing seems like a more general primitive.

@sicking
Copy link

sicking commented Nov 25, 2015

If we signal to the page when a request is sent, especially if we do so through an in-memory object which goes away on page navigation, then that greatly reduces our ability to change the algorithm which we use to decide when the request is sent.

I.e. if we signal to the page when the request is sent, then the page will rely on the timing that we use.

This not only means that we need to precisely define the algorithm as to make sure that pages that work in one browser don't break in another, it also means that if we ever want to improve the algorithm, or use different algorithms when different types of connectivity is used, that we'll have to add a new API and leave all existing content behind.

I'd rather have a better solution than that.

@igrigorik
Copy link
Member Author

I don't follow. Why do we need to signal when the request is sent? As a developer I issue a fetch() and get back a promise. The UA holds the dispatch for some (small) amount of time to enable batching and then dispatches the request as it normally would... From my perspective, the only difference is that I can observe that sometimes there is a delta between startTime and fetchStart for that request (which is the time it was sitting in the queue).

@sicking
Copy link

sicking commented Nov 26, 2015

By "signal" I mean fire some form of callback. Which is what resolving a promise is.

@igrigorik
Copy link
Member Author

I'm not convinced that's necessary. The promise will be resolved once (and if) the request is finished. We don't provide any other callbacks for "request has started" for regular requests, and regular requests can and already are delayed in some situations -- e.g. initiate 6 fetches against an http/1-only origin and then initiate a 7th request, its dispatch will be delayed until one of the connections becomes available. From an observer's point of view, the blocked case is pretty much indistinguishable from the coalescing behavior we're describing here.

@sicking
Copy link

sicking commented Nov 26, 2015

The delay of the 7th request is consistent across browsers and across platforms. Which is why it's less of a problem.

You said in an earlier comment that you want this to be flexible and not define the exact timing too precisely. We won't be able to accomplish that if we fire a callback when the request goes out.

@annevk
Copy link
Member

annevk commented Nov 26, 2015

@sicking it is clear that we currently don't signal when a request is transmitted, right? The first signal arrives when a response's headers are parsed. (This may change to some extent with streams, but those seem incompatible with coalescing anyway.)

@igrigorik
Copy link
Member Author

The delay of the 7th request is consistent across browsers and across platforms. Which is why it's less of a problem.

It's not. 6 connections is a loose rule of thumb and some browsers adjust this number - e.g. afaik, wininet has some smarts in it that ramps this number up and down. Similarly, some browsers may delay and reorder dispatch of requests based on when its initiated with respect to page load, network conditions, etc. Long story short, exactly as @annevk said.. :-)

@sicking
Copy link

sicking commented Nov 27, 2015

I don't mean to claim that we indicate when a request is started. But we certainly signal when a request finishes which is heavily correlated with when it is started.

But to be more correct, I should say that as long as we signal when a request is started, finished, or reaches any other state change, then webpages will come to depend on the timing of those state changes.

@igrigorik
Copy link
Member Author

Taking a step back.. I think we're down into the weeds of how such a thing should be implemented. Before we get to that, I wanted to gauge whether such capability makes sense as part of Fetch?

Based on above, I'm not hearing any loud objections. Should we move this discussion to Fetch repo?

@toddreifsteck
Copy link
Member

I believe simply stating that a beacon CAN be deferred and how long (for privacy) is all that the Beacon spec needs to state. The specifics of how the internals function don't seem strictly necessary as part of the Beacon spec.

@igrigorik
Copy link
Member Author

@toddreifsteck yep. I believe http://w3c.github.io/beacon/#sec-sendBeacon-method already covers that.

Let's move this discussion to fetch: whatwg/fetch#184. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants